From 654bf4c0dea94bc7cf6b2ad50bbc12de51450925 Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 7 Feb 2024 10:59:17 +0530 Subject: [PATCH 01/29] create recurring event helpers and refactor --- README.md | 9 +- docker-compose.dev.yaml | 18 +++ package-lock.json | 17 +++ package.json | 1 + schema.graphql | 28 +++- src/constants.ts | 13 ++ .../associateEventWithUser.ts | 38 ++++++ .../createRecurringEvents.ts | 77 +++++++++++ .../createEventHelpers/createSingleEvent.ts | 25 ++++ src/helpers/event/createEventHelpers/index.ts | 5 + .../createRecurrenceRule.ts | 48 +++++++ .../generateRecurrenceRuleString.ts | 0 .../generateRecurringEventInstances.ts | 65 +++++++++ .../getRecurringInstanceDates.ts | 35 +++++ .../event/recurringEventHelpers/index.ts | 9 ++ src/helpers/eventInstances/index.ts | 4 - src/helpers/eventInstances/once.ts | 29 ---- src/helpers/eventInstances/weekly.ts | 53 -------- src/models/Event.ts | 43 +++++- src/models/RecurrenceRule.ts | 85 ++++++++++++ src/resolvers/Mutation/createEvent.ts | 128 +++++++----------- .../Query/eventsByOrganizationConnection.ts | 1 + src/typeDefs/enums.ts | 18 +++ src/typeDefs/inputs.ts | 7 + src/typeDefs/mutations.ts | 3 +- src/typeDefs/types.ts | 2 +- src/types/generatedGraphQLTypes.ts | 35 ++++- tests/resolvers/Mutation/createEvent.spec.ts | 20 ++- 28 files changed, 633 insertions(+), 183 deletions(-) create mode 100644 docker-compose.dev.yaml create mode 100644 src/helpers/event/createEventHelpers/associateEventWithUser.ts create mode 100644 src/helpers/event/createEventHelpers/createRecurringEvents.ts create mode 100644 src/helpers/event/createEventHelpers/createSingleEvent.ts create mode 100644 src/helpers/event/createEventHelpers/index.ts create mode 100644 src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts create mode 100644 src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts create mode 100644 src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts create mode 100644 src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts create mode 100644 src/helpers/event/recurringEventHelpers/index.ts delete mode 100644 src/helpers/eventInstances/index.ts delete mode 100644 src/helpers/eventInstances/once.ts delete mode 100644 src/helpers/eventInstances/weekly.ts create mode 100644 src/models/RecurrenceRule.ts diff --git a/README.md b/README.md index 17c17f4626..bdacd57db5 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,10 @@ Core features include: -- [Talawa API](#talawa-api) - - [Talawa Components](#talawa-components) - - [Documentation](#documentation) - - [Installation](#installation) - - [Image Upload](#image-upload) +- [Talawa Components](#talawa-components) +- [Documentation](#documentation) +- [Installation](#installation) +- [Image Upload](#image-upload) diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000000..85a01eabc1 --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,18 @@ +services: + mongodb: + image: mongo:latest + ports: + - 27017:27017 + volumes: + - mongodb-data:/data/db + + redis-stack-server: + image: redis/redis-stack-server:latest + ports: + - 6379:6379 + volumes: + - redis-data:/data/redis + +volumes: + mongodb-data: + redis-data: diff --git a/package-lock.json b/package-lock.json index 310635df68..166efec1c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "nodemailer": "^6.9.8", "pm2": "^5.2.0", "redis": "^4.6.12", + "rrule": "^2.8.1", "shortid": "^2.2.16", "typedoc-plugin-markdown": "^3.17.1", "uuid": "^9.0.0", @@ -15025,6 +15026,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrule": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", + "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -28047,6 +28056,14 @@ "fsevents": "~2.3.2" } }, + "rrule": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", + "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==", + "requires": { + "tslib": "^2.4.0" + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", diff --git a/package.json b/package.json index 85929d2cf2..00fa10413a 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "nodemailer": "^6.9.8", "pm2": "^5.2.0", "redis": "^4.6.12", + "rrule": "^2.8.1", "shortid": "^2.2.16", "typedoc-plugin-markdown": "^3.17.1", "uuid": "^9.0.0", diff --git a/schema.graphql b/schema.graphql index 8ea527723c..716954fe31 100644 --- a/schema.graphql +++ b/schema.graphql @@ -253,7 +253,7 @@ type Event { createdAt: DateTime! creator: User description: String! - endDate: Date! + endDate: Date endTime: Time feedback: [Feedback!]! isPublic: Boolean! @@ -288,6 +288,7 @@ input EventInput { longitude: Longitude organizationId: ID! recurrance: Recurrance + recurrenceRuleString: String recurring: Boolean! startDate: Date! startTime: Time @@ -376,6 +377,13 @@ input ForgotPasswordData { userOtp: String! } +enum Frequency { + DAILY + MONTHLY + WEEKLY + YEARLY +} + enum Gender { FEMALE MALE @@ -535,7 +543,7 @@ type Mutation { createComment(data: CommentInput!, postId: ID!): Comment createDirectChat(data: createChatInput!): DirectChat! createDonation(amount: Float!, nameOfOrg: String!, nameOfUser: String!, orgId: ID!, payPalId: ID!, userId: ID!): Donation! - createEvent(data: EventInput): Event! + createEvent(data: EventInput!, recurrenceRule: RecurrenceRuleInput): Event! createGroupChat(data: createGroupChatInput!): GroupChat! createMember(input: UserAndOrganizationInput!): Organization! createMessageChat(data: MessageChatInput!): MessageChat! @@ -894,6 +902,12 @@ enum Recurrance { YEARLY } +input RecurrenceRuleInput { + count: Int + frequency: Frequency + weekdays: [WeekDays] +} + enum Status { ACTIVE BLOCKED @@ -1195,6 +1209,16 @@ type UsersConnectionResult { errors: [ConnectionError!]! } +enum WeekDays { + FR + MO + SA + SU + TH + TU + WE +} + input createChatInput { organizationId: ID! userIds: [ID!]! diff --git a/src/constants.ts b/src/constants.ts index e5945d7454..742c299100 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -517,6 +517,19 @@ export const REDIS_PASSWORD = process.env.REDIS_PASSWORD; export const MILLISECONDS_IN_A_WEEK = 7 * 24 * 60 * 60 * 1000; +export const RECURRING_EVENT_INSTANCES_MONTH_LIMIT = 2; +export const RECURRING_EVENT_INSTANCES_YEAR_LIMIT = 1; +export const RECURRENCE_FREQUENCIES = ["YEARLY", "MONTHLY", "WEEKLY", "DAILY"]; +export const RECURRENCE_WEEKDAYS = [ + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", + "SUNDAY", +]; + export const key = ENV.ENCRYPTION_KEY as string; export const iv = crypto.randomBytes(16).toString("hex"); diff --git a/src/helpers/event/createEventHelpers/associateEventWithUser.ts b/src/helpers/event/createEventHelpers/associateEventWithUser.ts new file mode 100644 index 0000000000..933c524d36 --- /dev/null +++ b/src/helpers/event/createEventHelpers/associateEventWithUser.ts @@ -0,0 +1,38 @@ +import type mongoose from "mongoose"; +import type { + InterfaceEvent, + InterfaceUser} from "../../../models"; +import { + EventAttendee, + User, +} from "../../../models"; + +export const associateEventWithUser = async ( + currentUser: InterfaceUser, + createdEvent: InterfaceEvent, + session: mongoose.ClientSession +): Promise => { + await EventAttendee.create( + [ + { + userId: currentUser._id.toString(), + eventId: createdEvent._id, + }, + ], + { session } + ); + + await User.updateOne( + { + _id: currentUser._id, + }, + { + $push: { + eventAdmin: createdEvent._id, + createdEvents: createdEvent._id, + registeredEvents: createdEvent._id, + }, + }, + { session } + ); +}; diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts new file mode 100644 index 0000000000..cfeb89c55f --- /dev/null +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -0,0 +1,77 @@ +import type mongoose from "mongoose"; +import type { InterfaceEvent } from "../../../models"; +import { Event } from "../../../models"; +import type { EventInput } from "../../../types/generatedGraphQLTypes"; +import { + createRecurrenceRule, + generateRecurringEventInstances, + getRecurringInstanceDates, +} from "../recurringEventHelpers"; +import { errors, requestContext } from "../../../libraries"; +import { FIELD_NON_EMPTY_ERROR } from "../../../constants"; + +export const createRecurringEvents = async ( + data: EventInput, + currentUserId: string, + organizationId: string, + session: mongoose.ClientSession +): Promise => { + if (!data.recurrenceRuleString) { + throw new errors.InputValidationError( + requestContext.translate(FIELD_NON_EMPTY_ERROR.MESSAGE), + FIELD_NON_EMPTY_ERROR.CODE, + FIELD_NON_EMPTY_ERROR.PARAM + ); + } + + const { recurrenceRuleString, ...eventData } = data; + + // create a base recurring event first, based on which all the + // recurring instances would be generated + const baseRecurringEvent = await Event.create( + [ + { + ...eventData, + recurring: true, + isBaseRecurringEvent: true, + creatorId: currentUserId, + admins: [currentUserId], + 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, latestInstanceDate] = + getRecurringInstanceDates( + recurrenceRuleString, + data.startDate, + data.endDate + ); + + // create a recurrenceRule document that would contain the recurrence pattern + const recurrenceRule = await createRecurrenceRule( + recurrenceRuleString, + data.startDate, + data.endDate, + organizationId.toString(), + baseRecurringEvent[0]?._id.toString(), + latestInstanceDate, + session + ); + + // generate the recurring instances + const recurringEventInstances = await generateRecurringEventInstances({ + eventData, + baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), + recurrenceRuleId: recurrenceRule?._id.toString(), + recurringInstanceDates, + currentUserId: currentUserId.toString(), + organizationId: organizationId.toString(), + session, + }); + + return recurringEventInstances; +}; diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts new file mode 100644 index 0000000000..b1068d55b3 --- /dev/null +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -0,0 +1,25 @@ +import type mongoose from "mongoose"; +import type { InterfaceEvent } from "../../../models"; +import { Event } from "../../../models"; +import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; + +export const createSingleEvent = async ( + args: Partial, + currentUserId: string, + organizationId: string, + session: mongoose.ClientSession +): Promise> => { + const createdEvent = await Event.create( + [ + { + ...args.data, + creatorId: currentUserId, + admins: [currentUserId], + organization: organizationId, + }, + ], + { session } + ); + + return createdEvent[0]; +}; diff --git a/src/helpers/event/createEventHelpers/index.ts b/src/helpers/event/createEventHelpers/index.ts new file mode 100644 index 0000000000..24a949bedc --- /dev/null +++ b/src/helpers/event/createEventHelpers/index.ts @@ -0,0 +1,5 @@ +import { createSingleEvent } from "./createSingleEvent"; +import { associateEventWithUser } from "./associateEventWithUser"; +import { createRecurringEvents } from "./createRecurringEvents"; + +export { createSingleEvent, createRecurringEvents, associateEventWithUser }; diff --git a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts new file mode 100644 index 0000000000..a174783fdc --- /dev/null +++ b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts @@ -0,0 +1,48 @@ +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"; +import { format } from "date-fns"; + +export const createRecurrenceRule = async ( + recurrenceRuleString: string, + recurrenceStartDate: Date, + recurrenceEndDate: Date | null, + organizationId: string, + baseRecurringEventId: string, + latestInstanceDate: Date, + session: mongoose.ClientSession +): Promise => { + const recurrenceRuleObject = rrulestr(recurrenceRuleString as string); + + const weekDays: string[] = []; + for (const weekday of recurrenceRuleObject.options.byweekday) { + weekDays.push(RECURRENCE_WEEKDAYS[weekday]); + } + + const formattedLatestInstanceDate = format(latestInstanceDate, "yyyy-MM-dd"); + const frequency = RECURRENCE_FREQUENCIES[recurrenceRuleObject.options.freq]; + + const recurrenceRule = await RecurrenceRule.create( + [ + { + organizationId, + baseRecurringEventId, + recurrenceRuleString, + startDate: recurrenceStartDate, + endDate: recurrenceEndDate, + frequency, + count: recurrenceRuleObject.options.count, + weekDays, + latestInstanceDate: formattedLatestInstanceDate, + }, + ], + { session } + ); + + return recurrenceRule[0].toObject(); +}; diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts new file mode 100644 index 0000000000..0321b44b28 --- /dev/null +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -0,0 +1,65 @@ +import type mongoose from "mongoose"; +import { format } from "date-fns"; +import type { InterfaceEvent } from "../../../models"; +import { Event } from "../../../models"; +import type { EventInput } from "../../../types/generatedGraphQLTypes"; + +interface InterfaceGenerateRecurringInstances { + eventData: EventInput; + baseRecurringEventId: string; + recurrenceRuleId: string; + recurringInstanceDates: Date[]; + currentUserId: string; + organizationId: string; + session: mongoose.ClientSession; +} + +interface InterfaceRecurringEvent extends EventInput { + isBaseRecurringEvent: boolean; + recurrenceRuleId: string; + baseRecurringEventId: string; + creatorId: string; + admins: string[]; + organization: string; +} + +export const generateRecurringEventInstances = async ({ + eventData, + baseRecurringEventId, + recurrenceRuleId, + recurringInstanceDates, + currentUserId, + organizationId, + session, +}: InterfaceGenerateRecurringInstances): Promise => { + const recurringInstances: InterfaceRecurringEvent[] = []; + recurringInstanceDates.map((date) => { + const formattedInstanceDate = format(date, "yyyy-MM-dd"); + + const createdEventInstance = { + ...eventData, + startDate: formattedInstanceDate, + endDate: formattedInstanceDate, + recurring: true, + isBaseRecurringEvent: false, + recurrenceRuleId: recurrenceRuleId, + baseRecurringEventId: baseRecurringEventId, + creatorId: currentUserId, + admins: [currentUserId], + organization: organizationId, + }; + + recurringInstances.push(createdEventInstance); + }); + + //Bulk insertion in database + let recurringEventInstances = await Event.insertMany(recurringInstances, { + session, + }); + + recurringEventInstances = Array.isArray(recurringEventInstances) + ? recurringEventInstances + : [recurringEventInstances]; + + return recurringEventInstances; +}; diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts new file mode 100644 index 0000000000..a528c0f2d5 --- /dev/null +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -0,0 +1,35 @@ +import { addMonths } from "date-fns"; +import { rrulestr } from "rrule"; +import { RECURRING_EVENT_INSTANCES_MONTH_LIMIT } from "../../../constants"; + +export function getRecurringInstanceDates( + recurrenceRuleString: string, + recurrenceStartDate: Date, + eventEndDate: Date | null, + calendarDate: Date = recurrenceStartDate +): [Date[], Date] { + const limitEndDate = addMonths( + calendarDate, + RECURRING_EVENT_INSTANCES_MONTH_LIMIT + // generate instances upto these many months ahead + // leave the rest for dynamic generation during queries + ); + eventEndDate = eventEndDate || limitEndDate; + + const generateUptoDate = new Date( + Math.min(eventEndDate.getTime(), limitEndDate.getTime()) + ); + + const recurrenceRuleObject = rrulestr(recurrenceRuleString); + + const recurringInstanceDates = recurrenceRuleObject.between( + recurrenceStartDate, + generateUptoDate, + true + ); + + const latestInstanceDate = + recurringInstanceDates[recurringInstanceDates.length - 1]; + + return [recurringInstanceDates, latestInstanceDate]; +} diff --git a/src/helpers/event/recurringEventHelpers/index.ts b/src/helpers/event/recurringEventHelpers/index.ts new file mode 100644 index 0000000000..ffff1fdec6 --- /dev/null +++ b/src/helpers/event/recurringEventHelpers/index.ts @@ -0,0 +1,9 @@ +import { createRecurrenceRule } from "./createRecurrenceRule"; +import { generateRecurringEventInstances } from "./generateRecurringEventInstances"; +import { getRecurringInstanceDates } from "./getRecurringInstanceDates"; + +export { + createRecurrenceRule, + generateRecurringEventInstances, + getRecurringInstanceDates, +}; diff --git a/src/helpers/eventInstances/index.ts b/src/helpers/eventInstances/index.ts deleted file mode 100644 index 75a88f1a7c..0000000000 --- a/src/helpers/eventInstances/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as Once from "./once"; -import * as Weekly from "./weekly"; - -export { Once, Weekly }; diff --git a/src/helpers/eventInstances/once.ts b/src/helpers/eventInstances/once.ts deleted file mode 100644 index 4a844b4841..0000000000 --- a/src/helpers/eventInstances/once.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type mongoose from "mongoose"; -import type { - InterfaceEvent, - InterfaceOrganization, - InterfaceUser, -} from "../../models"; -import { Event } from "../../models"; -import type { MutationCreateEventArgs } from "../../types/generatedGraphQLTypes"; - -export async function generateEvent( - args: Partial, - currentUser: InterfaceUser, - organization: InterfaceOrganization, - session: mongoose.ClientSession -): Promise> { - const createdEvent = await Event.create( - [ - { - ...args.data, - creatorId: currentUser._id, - admins: [currentUser._id], - organization: organization._id, - }, - ], - { session } - ); - - return createdEvent; -} diff --git a/src/helpers/eventInstances/weekly.ts b/src/helpers/eventInstances/weekly.ts deleted file mode 100644 index 716fb7dcc3..0000000000 --- a/src/helpers/eventInstances/weekly.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type mongoose from "mongoose"; -import type { - InterfaceEvent, - InterfaceOrganization, - InterfaceUser, -} from "../../models"; -import { Event } from "../../models"; -import type { MutationCreateEventArgs } from "../../types/generatedGraphQLTypes"; -import { eachDayOfInterval, format } from "date-fns"; - -interface InterfaceRecurringEvent extends MutationCreateEventArgs { - startDate: Date; - creatorId: mongoose.Types.ObjectId; - admins: mongoose.Types.ObjectId[]; - organization: mongoose.Types.ObjectId; -} - -export async function generateEvents( - args: Partial, - currentUser: InterfaceUser, - organization: InterfaceOrganization, - session: mongoose.ClientSession -): Promise { - const recurringEvents: InterfaceRecurringEvent[] = []; - const { data } = args; - - const startDate = new Date(data?.startDate); - const endDate = new Date(data?.endDate); - - const allDays = eachDayOfInterval({ start: startDate, end: endDate }); - const occurrences = allDays.filter( - (date) => date.getDay() === startDate.getDay() - ); - - occurrences.map((date) => { - const formattedDate = format(date, "yyyy-MM-dd"); - - const createdEvent = { - ...data, - startDate: new Date(formattedDate), - creatorId: currentUser._id, - admins: [currentUser._id], - organization: organization._id, - }; - - recurringEvents.push(createdEvent); - }); - - //Bulk insertion in database - const weeklyEvents = await Event.insertMany(recurringEvents, { session }); - - return Array.isArray(weeklyEvents) ? weeklyEvents : [weeklyEvents]; -} diff --git a/src/models/Event.ts b/src/models/Event.ts index 05b72ac0f5..03920b7a4e 100644 --- a/src/models/Event.ts +++ b/src/models/Event.ts @@ -2,6 +2,7 @@ import type { Types, PopulatedDoc, Document, Model } from "mongoose"; import { Schema, model, models } from "mongoose"; import type { InterfaceOrganization } from "./Organization"; import type { InterfaceUser } from "./User"; +import type { InterfaceRecurrenceRule } from "./RecurrenceRule"; /** * This is an interface representing a document for an event in the database(MongoDB). @@ -15,6 +16,10 @@ export interface InterfaceEvent { latitude: number | undefined; longitude: number; recurring: boolean; + isRecurringEventException: boolean; + isBaseRecurringEvent: boolean; + recurrenceRuleId: PopulatedDoc; + baseRecurringEventId: PopulatedDoc; allDay: boolean; startDate: string; endDate: string | undefined; @@ -40,6 +45,10 @@ export interface InterfaceEvent { * @param latitude - Latitude * @param longitude - Longitude * @param recurring - Is the event recurring + * @param isRecurringEventException - Is the event an exception to the recurring pattern it was following + * @param isBaseRecurringEvent - Is the event a true recurring event that is used for generating new instances + * @param recurrenceRuleId - Id of the recurrence rule document containing the recurrence pattern for the event + * @param baseRecurringEventId - Id of the true recurring event used for generating this instance * @param allDay - Is the event occuring all day * @param startDate - Start Date * @param endDate - End date @@ -86,6 +95,32 @@ const eventSchema = new Schema( required: true, default: false, }, + isRecurringEventException: { + type: Boolean, + required: true, + default: false, + }, + isBaseRecurringEvent: { + type: Boolean, + required: true, + default: false, + }, + recurrenceRuleId: { + type: Schema.Types.ObjectId, + ref: "RecurrenceRule", + required: function (): () => boolean { + // @ts-expect-error Suppressing typescript error for conditional required field + return this.recurring && !this.isBaseRecurringEvent; + }, + }, + baseRecurringEventId: { + type: Schema.Types.ObjectId, + ref: "Event", + required: function (): () => boolean { + // @ts-expect-error Suppressing typescript error for conditional required field + return this.recurring && !this.isBaseRecurringEvent; + }, + }, allDay: { type: Boolean, required: true, @@ -97,28 +132,28 @@ const eventSchema = new Schema( endDate: { type: Date, required: function (): () => boolean { - // @ts-ignore + // @ts-expect-error Suppressing typescript error for conditional required field return !this.allDay; }, }, startTime: { type: Date, required: function (): () => boolean { - // @ts-ignore + // @ts-expect-error Suppressing typescript error for conditional required field return !this.allDay; }, }, endTime: { type: Date, required: function (): () => boolean { - // @ts-ignore + // @ts-expect-error Suppressing typescript error for conditional required field return !this.allDay; }, }, recurrance: { type: String, required: function (): () => boolean { - // @ts-ignore + // @ts-expect-error Suppressing typescript error for conditional required field return this.recurring; }, enum: ["ONCE", "DAILY", "WEEKLY", "MONTHLY", "YEARLY"], diff --git a/src/models/RecurrenceRule.ts b/src/models/RecurrenceRule.ts new file mode 100644 index 0000000000..b84ea8e3e9 --- /dev/null +++ b/src/models/RecurrenceRule.ts @@ -0,0 +1,85 @@ +import type { Types, PopulatedDoc, Document, Model } from "mongoose"; +import { Schema, model, models } from "mongoose"; +import type { InterfaceEvent } from "./Event"; + +/** + * This is an interface representing a document for a recurrence rule in the database(MongoDB). + */ + +export interface InterfaceRecurrenceRule { + _id: Types.ObjectId; + organizationId: Types.ObjectId; + baseRecurringEventId: PopulatedDoc; + recurrenceRuleString: string; + startDate: Date; + endDate: Date; + frequency: "YEARLY" | "MONTHLY" | "WEEKLY" | "DAILY"; + count: number; + weekDays: string[]; + latestInstanceDate: Date; +} + +/** + * This is the Structure of the RecurringEvent + * @param organizationId - _id of the organization the evevnts following this recurrence rule belong to + * @param baseRecurringEventId - _id of the base event common to the recurrence pattern + * @param recurrenceRuleString - An rrule string representing the recurrence pattern + * @param startDate - Start date of the recurrence pattern (not necessarily the startDate of the first recurring instance) + * @param endDate - Start date of the recurrence pattern (not necessarily the startDate of the last recurring instance) + * @param frequency - Frequency of recurrence + * @param count - Number of recurring instances + * @param weekDays - Array containing the days of the week the recurring instance occurs + * @param latestInstanceDate - The startDate of the last recurring instance generated using this recurrence rule + */ + +const recurrenceRuleSchema = new Schema( + { + organizationId: { + type: Schema.Types.ObjectId, + ref: "Organization", + required: true, + }, + baseRecurringEventId: { + type: Schema.Types.ObjectId, + ref: "Event", + required: true, + }, + recurrenceRuleString: { + type: String, + required: true, + }, + startDate: { + type: Date, + required: true, + }, + endDate: { + type: Date, + }, + frequency: { + type: String, + required: true, + enum: ["YEARLY", "MONTHLY", "WEEKLY", "DAILY"], + }, + count: { + type: Number, + }, + weekDays: [ + { + type: String, + }, + ], + latestInstanceDate: { + type: Date, + }, + }, + { + timestamps: true, + } +); + +const recurrenceRuleModel = (): Model => + model("RecurrenceRule", recurrenceRuleSchema); + +// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +export const RecurrenceRule = (models.RecurrenceRule || + recurrenceRuleModel()) as ReturnType; diff --git a/src/resolvers/Mutation/createEvent.ts b/src/resolvers/Mutation/createEvent.ts index d05e10a43e..bbcc370b52 100644 --- a/src/resolvers/Mutation/createEvent.ts +++ b/src/resolvers/Mutation/createEvent.ts @@ -1,6 +1,6 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; import { errors, requestContext } from "../../libraries"; -import type { InterfaceEvent, InterfaceUser } from "../../models"; +import type { InterfaceEvent } from "../../models"; import { User, Organization } from "../../models"; import { USER_NOT_FOUND_ERROR, @@ -10,23 +10,28 @@ import { } from "../../constants"; import { isValidString } from "../../libraries/validators/validateString"; import { compareDates } from "../../libraries/validators/compareDates"; -import { EventAttendee } from "../../models/EventAttendee"; import { cacheEvents } from "../../services/EventCache/cacheEvents"; -import type mongoose from "mongoose"; import { session } from "../../db"; -import { Weekly, Once } from "../../helpers/eventInstances"; +import { + createSingleEvent, + createRecurringEvents, + associateEventWithUser, +} from "../../helpers/event/createEventHelpers"; /** * This function enables to create an event. * @param _parent - parent of current request * @param args - payload provided with the request * @param context - context of entire application - * @remarks The following checks are done: - * 1. If the user exists - * 2. If the organization exists - * 3. If the user is a part of the organization. + * @remarks The following steps are followed: + * 1. Check if the user exists + * 2. Check if the organization exists + * 3. Check if the user is a part of the organization. + * 4. If the event is recurring, create the recurring event instances. + * 5. If the event is non-recurring, create a single event. * @returns Created event */ + export const createEvent: MutationResolvers["createEvent"] = async ( _parent, args, @@ -128,94 +133,59 @@ export const createEvent: MutationResolvers["createEvent"] = async ( } try { - let createdEvent!: InterfaceEvent[]; - - if (args.data?.recurring) { - switch (args.data?.recurrance) { - case "ONCE": - createdEvent = await Once.generateEvent( - args, - currentUser, - organization, - session - ); - - for (const event of createdEvent) { - await associateEventWithUser(currentUser, event, session); - await cacheEvents([event]); - } - - break; - - case "WEEKLY": - createdEvent = await Weekly.generateEvents( - args, - currentUser, - organization, - session - ); - - for (const event of createdEvent) { - await associateEventWithUser(currentUser, event, session); - await cacheEvents([event]); - } - - break; + let createdEvent: InterfaceEvent; + + const { data } = args; + + if (args.data.recurring) { + // generate recurring events upto a date limit, + // leave the rest for the query + + // create recurring event instances + const recurringEventInstances = await createRecurringEvents( + data, + currentUser?._id.toString(), + organization?._id.toString(), + session + ); + + // associate recurring event instances with the current user and cache them + for (const recurringEventInstance of recurringEventInstances) { + await associateEventWithUser( + currentUser, + recurringEventInstance, + session + ); + await cacheEvents([recurringEventInstance]); } + + createdEvent = recurringEventInstances[0]; } else { - createdEvent = await Once.generateEvent( + // create a single non-recurring event + createdEvent = await createSingleEvent( args, - currentUser, - organization, + currentUser?._id.toString(), + organization?._id.toString(), session ); - for (const event of createdEvent) { - await associateEventWithUser(currentUser, event, session); - await cacheEvents([event]); - } + // associate event with the current user and cache it + await associateEventWithUser(currentUser, createdEvent, session); + await cacheEvents([createdEvent]); } if (session) { + // commit transaction if everything's successful await session.commitTransaction(); } // Returns the createdEvent. - return createdEvent[0]; + return createdEvent; } catch (error) { if (session) { + // abort transaction if something fails await session.abortTransaction(); } throw error; } }; - -async function associateEventWithUser( - currentUser: InterfaceUser, - createdEvent: InterfaceEvent, - session: mongoose.ClientSession -): Promise { - await EventAttendee.create( - [ - { - userId: currentUser._id.toString(), - eventId: createdEvent._id, - }, - ], - { session } - ); - - await User.updateOne( - { - _id: currentUser._id, - }, - { - $push: { - eventAdmin: createdEvent._id, - createdEvents: createdEvent._id, - registeredEvents: createdEvent._id, - }, - }, - { session } - ); -} diff --git a/src/resolvers/Query/eventsByOrganizationConnection.ts b/src/resolvers/Query/eventsByOrganizationConnection.ts index 5ad8d4248b..ba0faa6d9c 100644 --- a/src/resolvers/Query/eventsByOrganizationConnection.ts +++ b/src/resolvers/Query/eventsByOrganizationConnection.ts @@ -12,6 +12,7 @@ export const eventsByOrganizationConnection: QueryResolvers["eventsByOrganizatio where = { ...where, status: "ACTIVE", + isBaseRecurringEvent: false, }; const events = await Event.find(where) diff --git a/src/typeDefs/enums.ts b/src/typeDefs/enums.ts index 9e9804c137..55b354eca5 100644 --- a/src/typeDefs/enums.ts +++ b/src/typeDefs/enums.ts @@ -25,6 +25,13 @@ export const enums = gql` location_DESC } + enum Frequency { + YEARLY + MONTHLY + WEEKLY + DAILY + } + enum OrganizationOrderByInput { id_ASC id_DESC @@ -100,6 +107,17 @@ export const enums = gql` SUPERADMIN NON_USER } + + enum WeekDays { + MO + TU + WE + TH + FR + SA + SU + } + enum EducationGrade { NO_GRADE PRE_KG diff --git a/src/typeDefs/inputs.ts b/src/typeDefs/inputs.ts index e4aa13d73e..1fda1a1c65 100644 --- a/src/typeDefs/inputs.ts +++ b/src/typeDefs/inputs.ts @@ -70,6 +70,7 @@ export const inputs = gql` startDate: Date! endDate: Date startTime: Time + recurrenceRuleString: String endTime: Time allDay: Boolean! recurring: Boolean! @@ -239,6 +240,12 @@ export const inputs = gql` recaptchaToken: String! } + input RecurrenceRuleInput { + frequency: Frequency + weekdays: [WeekDays] + count: Int + } + input ToggleUserTagAssignInput { userId: ID! tagId: ID! diff --git a/src/typeDefs/mutations.ts b/src/typeDefs/mutations.ts index 1843e92141..cdd6dd4bd5 100644 --- a/src/typeDefs/mutations.ts +++ b/src/typeDefs/mutations.ts @@ -79,7 +79,8 @@ export const mutations = gql` nameOfOrg: String! ): Donation! - createEvent(data: EventInput): Event! @auth + createEvent(data: EventInput!, recurrenceRule: RecurrenceRuleInput): Event! + @auth createGroupChat(data: createGroupChatInput!): GroupChat! @auth diff --git a/src/typeDefs/types.ts b/src/typeDefs/types.ts index c58764ccbe..a89a6ebafb 100644 --- a/src/typeDefs/types.ts +++ b/src/typeDefs/types.ts @@ -145,7 +145,7 @@ export const types = gql` title: String! description: String! startDate: Date! - endDate: Date! + endDate: Date startTime: Time endTime: Time allDay: Boolean! diff --git a/src/types/generatedGraphQLTypes.ts b/src/types/generatedGraphQLTypes.ts index fd68a701a1..0c2f1598ba 100644 --- a/src/types/generatedGraphQLTypes.ts +++ b/src/types/generatedGraphQLTypes.ts @@ -307,7 +307,7 @@ export type Event = { createdAt: Scalars['DateTime']['output']; creator?: Maybe; description: Scalars['String']['output']; - endDate: Scalars['Date']['output']; + endDate?: Maybe; endTime?: Maybe; feedback: Array; isPublic: Scalars['Boolean']['output']; @@ -347,6 +347,7 @@ export type EventInput = { longitude?: InputMaybe; organizationId: Scalars['ID']['input']; recurrance?: InputMaybe; + recurrenceRuleString?: InputMaybe; recurring: Scalars['Boolean']['input']; startDate: Scalars['Date']['input']; startTime?: InputMaybe; @@ -436,6 +437,12 @@ export type ForgotPasswordData = { userOtp: Scalars['String']['input']; }; +export type Frequency = + | 'DAILY' + | 'MONTHLY' + | 'WEEKLY' + | 'YEARLY'; + export type Gender = | 'FEMALE' | 'MALE' @@ -813,7 +820,8 @@ export type MutationCreateDonationArgs = { export type MutationCreateEventArgs = { - data?: InputMaybe; + data: EventInput; + recurrenceRule?: InputMaybe; }; @@ -1640,6 +1648,12 @@ export type Recurrance = | 'WEEKLY' | 'YEARLY'; +export type RecurrenceRuleInput = { + count?: InputMaybe; + frequency?: InputMaybe; + weekdays?: InputMaybe>>; +}; + export type Status = | 'ACTIVE' | 'BLOCKED' @@ -1966,6 +1980,15 @@ export type UsersConnectionResult = { errors: Array; }; +export type WeekDays = + | 'FR' + | 'MO' + | 'SA' + | 'SU' + | 'TH' + | 'TU' + | 'WE'; + export type CreateChatInput = { organizationId: Scalars['ID']['input']; userIds: Array; @@ -2097,6 +2120,7 @@ export type ResolversTypes = { FieldError: ResolverTypeWrapper['FieldError']>; Float: ResolverTypeWrapper; ForgotPasswordData: ForgotPasswordData; + Frequency: Frequency; Gender: Gender; Group: ResolverTypeWrapper; GroupChat: ResolverTypeWrapper; @@ -2146,6 +2170,7 @@ export type ResolversTypes = { Query: ResolverTypeWrapper<{}>; RecaptchaVerification: RecaptchaVerification; Recurrance: Recurrance; + RecurrenceRuleInput: RecurrenceRuleInput; Status: Status; String: ResolverTypeWrapper; Subscription: ResolverTypeWrapper<{}>; @@ -2186,6 +2211,7 @@ export type ResolversTypes = { UsersConnection: ResolverTypeWrapper & { edges: Array }>; UsersConnectionInput: UsersConnectionInput; UsersConnectionResult: ResolverTypeWrapper & { data?: Maybe, errors: Array }>; + WeekDays: WeekDays; createChatInput: CreateChatInput; createGroupChatInput: CreateGroupChatInput; }; @@ -2275,6 +2301,7 @@ export type ResolversParentTypes = { PostWhereInput: PostWhereInput; Query: {}; RecaptchaVerification: RecaptchaVerification; + RecurrenceRuleInput: RecurrenceRuleInput; String: Scalars['String']['output']; Subscription: {}; Time: Scalars['Time']['output']; @@ -2517,7 +2544,7 @@ export type EventResolvers; creator?: Resolver, ParentType, ContextType>; description?: Resolver; - endDate?: Resolver; + endDate?: Resolver, ParentType, ContextType>; endTime?: Resolver, ParentType, ContextType>; feedback?: Resolver, ParentType, ContextType>; isPublic?: Resolver; @@ -2705,7 +2732,7 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; createDirectChat?: Resolver>; createDonation?: Resolver>; - createEvent?: Resolver>; + createEvent?: Resolver>; createGroupChat?: Resolver>; createMember?: Resolver>; createMessageChat?: Resolver>; diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 9a20ba9884..42cb99f4cf 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -64,7 +64,25 @@ afterAll(async () => { describe("resolvers -> Mutation -> createEvent", () => { it(`throws NotFoundError if no user exists with _id === context.userId`, async () => { try { - const args: MutationCreateEventArgs = {}; + const args: MutationCreateEventArgs = { + data: { + organizationId: testOrganization?.id, + allDay: false, + description: "", + endDate: "", + endTime: "", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "", + recurring: false, + startDate: "", + startTime: "", + title: "", + recurrance: "ONCE", + }, + }; const context = { userId: Types.ObjectId().toString(), From 9ae6d1da54645ca8688d830c9589b3bd0492c804 Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 7 Feb 2024 11:13:57 +0530 Subject: [PATCH 02/29] remove unnecessary file --- docker-compose.dev.yaml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 docker-compose.dev.yaml diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml deleted file mode 100644 index 85a01eabc1..0000000000 --- a/docker-compose.dev.yaml +++ /dev/null @@ -1,18 +0,0 @@ -services: - mongodb: - image: mongo:latest - ports: - - 27017:27017 - volumes: - - mongodb-data:/data/db - - redis-stack-server: - image: redis/redis-stack-server:latest - ports: - - 6379:6379 - volumes: - - redis-data:/data/redis - -volumes: - mongodb-data: - redis-data: From 085880af168f68478463c67afd7032279e9554dc Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 7 Feb 2024 14:35:20 +0530 Subject: [PATCH 03/29] add documentation and comments --- schema.graphql | 3 +- src/constants.ts | 3 +- .../associateEventWithUser.ts | 33 +++++---- .../createRecurringEvents.ts | 60 ++++++++++++---- .../createEventHelpers/createSingleEvent.ts | 22 ++++++ src/helpers/event/createEventHelpers/index.ts | 3 +- .../createRecurrenceRule.ts | 15 ++++ .../generateRecurrenceRuleString.ts | 72 +++++++++++++++++++ .../generateRecurringEventInstances.ts | 20 +++++- .../getRecurringInstanceDates.ts | 16 ++++- src/resolvers/Mutation/createEvent.ts | 23 +----- src/typeDefs/inputs.ts | 1 - src/typeDefs/mutations.ts | 6 +- src/types/generatedGraphQLTypes.ts | 3 +- 14 files changed, 214 insertions(+), 66 deletions(-) diff --git a/schema.graphql b/schema.graphql index 716954fe31..ee85c528d9 100644 --- a/schema.graphql +++ b/schema.graphql @@ -288,7 +288,6 @@ input EventInput { longitude: Longitude organizationId: ID! recurrance: Recurrance - recurrenceRuleString: String recurring: Boolean! startDate: Date! startTime: Time @@ -543,7 +542,7 @@ type Mutation { createComment(data: CommentInput!, postId: ID!): Comment createDirectChat(data: createChatInput!): DirectChat! createDonation(amount: Float!, nameOfOrg: String!, nameOfUser: String!, orgId: ID!, payPalId: ID!, userId: ID!): Donation! - createEvent(data: EventInput!, recurrenceRule: RecurrenceRuleInput): Event! + createEvent(data: EventInput!, recurrenceRuleData: RecurrenceRuleInput): Event! createGroupChat(data: createGroupChatInput!): GroupChat! createMember(input: UserAndOrganizationInput!): Organization! createMessageChat(data: MessageChatInput!): MessageChat! diff --git a/src/constants.ts b/src/constants.ts index 742c299100..6501e5f4f6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -517,8 +517,7 @@ export const REDIS_PASSWORD = process.env.REDIS_PASSWORD; export const MILLISECONDS_IN_A_WEEK = 7 * 24 * 60 * 60 * 1000; -export const RECURRING_EVENT_INSTANCES_MONTH_LIMIT = 2; -export const RECURRING_EVENT_INSTANCES_YEAR_LIMIT = 1; +export const RECURRING_EVENT_INSTANCES_MONTH_LIMIT = 6; export const RECURRENCE_FREQUENCIES = ["YEARLY", "MONTHLY", "WEEKLY", "DAILY"]; export const RECURRENCE_WEEKDAYS = [ "MONDAY", diff --git a/src/helpers/event/createEventHelpers/associateEventWithUser.ts b/src/helpers/event/createEventHelpers/associateEventWithUser.ts index 933c524d36..b4efbed105 100644 --- a/src/helpers/event/createEventHelpers/associateEventWithUser.ts +++ b/src/helpers/event/createEventHelpers/associateEventWithUser.ts @@ -1,22 +1,25 @@ import type mongoose from "mongoose"; -import type { - InterfaceEvent, - InterfaceUser} from "../../../models"; -import { - EventAttendee, - User, -} from "../../../models"; +import { EventAttendee, User } from "../../../models"; + +/** + * This function associates an event with the user. + * @param currentUserId - _id of the current user. + * @param createdEventId - _id of the event. + * @remarks The following steps are followed: + * 1. Create an EventAttendee (adding the user as an attendee). + * 2. Update the event related user fields. + */ export const associateEventWithUser = async ( - currentUser: InterfaceUser, - createdEvent: InterfaceEvent, + currentUserId: string, + createdEventId: string, session: mongoose.ClientSession ): Promise => { await EventAttendee.create( [ { - userId: currentUser._id.toString(), - eventId: createdEvent._id, + userId: currentUserId, + eventId: createdEventId, }, ], { session } @@ -24,13 +27,13 @@ export const associateEventWithUser = async ( await User.updateOne( { - _id: currentUser._id, + _id: currentUserId, }, { $push: { - eventAdmin: createdEvent._id, - createdEvents: createdEvent._id, - registeredEvents: createdEvent._id, + eventAdmin: createdEventId, + createdEvents: createdEventId, + registeredEvents: createdEventId, }, }, { session } diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts index cfeb89c55f..0f642123d8 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -1,37 +1,59 @@ import type mongoose from "mongoose"; import type { InterfaceEvent } from "../../../models"; import { Event } from "../../../models"; -import type { EventInput } from "../../../types/generatedGraphQLTypes"; +import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; import { createRecurrenceRule, generateRecurringEventInstances, getRecurringInstanceDates, } from "../recurringEventHelpers"; -import { errors, requestContext } from "../../../libraries"; -import { FIELD_NON_EMPTY_ERROR } from "../../../constants"; +import { generateRecurrenceRuleString } from "../recurringEventHelpers/generateRecurrenceRuleString"; +import { associateEventWithUser } from "./associateEventWithUser"; +import { cacheEvents } from "../../../services/EventCache/cacheEvents"; + +/** + * This function create the instances of a recurring event upto a certain date. + * @param args - payload of the createEvent mutation + * @param currentUserId - _id of the current user + * @param organizationId - _id of the organization the events belongs to + * @remarks The following steps are followed: + * 1. Creating a default recurrenceRuleData. + * 2. Generating a recurrence rule string based on the recurrenceRuleData. + * 3. Creating a baseRecurringEvent on which recurring instances would be based. + * 4. Getting the dates for recurring instances. + * 5. Creating a recurrenceRule document. + * 6. Generating recurring instances according to the recurrence rule. + * 7. Associating the instances with the user and caching them. + * @returns Created recurring event instances + */ export const createRecurringEvents = async ( - data: EventInput, + args: MutationCreateEventArgs, currentUserId: string, organizationId: string, session: mongoose.ClientSession ): Promise => { - if (!data.recurrenceRuleString) { - throw new errors.InputValidationError( - requestContext.translate(FIELD_NON_EMPTY_ERROR.MESSAGE), - FIELD_NON_EMPTY_ERROR.CODE, - FIELD_NON_EMPTY_ERROR.PARAM - ); + const { data } = args; + let { recurrenceRuleData } = args; + + if (!recurrenceRuleData) { + recurrenceRuleData = { + frequency: "WEEKLY", + }; } - const { recurrenceRuleString, ...eventData } = data; + const recurrenceRuleString = generateRecurrenceRuleString( + recurrenceRuleData, + data?.startDate, + data?.endDate + ); // create a base recurring event first, based on which all the - // recurring instances would be generated + // recurring instances would be dynamically generated const baseRecurringEvent = await Event.create( [ { - ...eventData, + ...data, recurring: true, isBaseRecurringEvent: true, creatorId: currentUserId, @@ -64,7 +86,7 @@ export const createRecurringEvents = async ( // generate the recurring instances const recurringEventInstances = await generateRecurringEventInstances({ - eventData, + data, baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), recurrenceRuleId: recurrenceRule?._id.toString(), recurringInstanceDates, @@ -73,5 +95,15 @@ export const createRecurringEvents = async ( session, }); + // associate the instances with the user and cache them + for (const recurringEventInstance of recurringEventInstances) { + await associateEventWithUser( + currentUserId, + recurringEventInstance?._id.toString(), + session + ); + await cacheEvents([recurringEventInstance]); + } + return recurringEventInstances; }; diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index b1068d55b3..10173e9a87 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -2,6 +2,19 @@ import type mongoose from "mongoose"; import type { InterfaceEvent } from "../../../models"; import { Event } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; +import { associateEventWithUser } from "./associateEventWithUser"; +import { cacheEvents } from "../../../services/EventCache/cacheEvents"; + +/** + * This function generates a single non-recurring event. + * @param args - the arguments provided for the createEvent mutation. + * @param currentUserId - _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 and cache it. + * @returns The event generated. + */ export const createSingleEvent = async ( args: Partial, @@ -9,6 +22,7 @@ export const createSingleEvent = async ( organizationId: string, session: mongoose.ClientSession ): Promise> => { + // create the single event const createdEvent = await Event.create( [ { @@ -21,5 +35,13 @@ export const createSingleEvent = async ( { session } ); + // associate the event with the user + await associateEventWithUser( + currentUserId, + createdEvent[0]?._id.toString(), + session + ); + await cacheEvents([createdEvent[0]]); + return createdEvent[0]; }; diff --git a/src/helpers/event/createEventHelpers/index.ts b/src/helpers/event/createEventHelpers/index.ts index 24a949bedc..0a3e77d301 100644 --- a/src/helpers/event/createEventHelpers/index.ts +++ b/src/helpers/event/createEventHelpers/index.ts @@ -1,5 +1,4 @@ import { createSingleEvent } from "./createSingleEvent"; -import { associateEventWithUser } from "./associateEventWithUser"; import { createRecurringEvents } from "./createRecurringEvents"; -export { createSingleEvent, createRecurringEvents, associateEventWithUser }; +export { createSingleEvent, createRecurringEvents }; diff --git a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts index a174783fdc..3f4cccc220 100644 --- a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts +++ b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts @@ -8,6 +8,21 @@ import { } from "../../../constants"; import { format } from "date-fns"; +/** + * This function generates the recurring event instances. + * @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, diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index e69de29bb2..62dfb76c40 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -0,0 +1,72 @@ +import { format } from "date-fns"; +import type { RecurrenceRuleInput } from "../../../types/generatedGraphQLTypes"; + +/** + * This function generates the recurring event instances. + * @param recurrenceRuleData - the recurrenceRuleInput provided in the args. + * @param recurrenceStartDate - start date of recurrence. + * @param recurrenceEndDate - end date of recurrence. + * @remarks The following steps are followed: + * 1. Initiate an empty recurrenceRule string. + * 2. Add the recurrence rules one by one. + * @returns The recurrence rule string that would be used to create a valid rrule object. + */ + +export const generateRecurrenceRuleString = ( + recurrenceRuleData: RecurrenceRuleInput, + recurrenceStartDate: Date, + recurrenceEndDate?: Date +): string => { + // Initiate an empty recurrenceRule string + let recurrenceRuleString = ""; + + const formattedRecurrenceStartDate = format( + recurrenceStartDate, + "yyyyMMdd'T'HHmmss'Z'" + ); + + recurrenceRuleString += "DTSTART:"; + // recurrence start date + // (not necessarily the start date of the first recurring instance) + recurrenceRuleString += `${formattedRecurrenceStartDate}\n`; + + // add recurrence rules one by one + recurrenceRuleString += "RRULE:"; + + // frequency of recurrence + // (defaulting to "WEEKLY" if recurrenceRule is not provided) + recurrenceRuleString += "FREQ="; + recurrenceRuleString += `${recurrenceRuleData.frequency}`; + + if (recurrenceEndDate) { + const formattedRecurrenceEndDate = format( + recurrenceEndDate, + "yyyyMMdd'T'HHmmss'Z'" + ); + + recurrenceRuleString += ";UNTIL="; + + // date upto which instances would be generated + recurrenceRuleString += `${formattedRecurrenceEndDate}`; + } + + if (recurrenceRuleData.count) { + recurrenceRuleString += ";COUNT="; + + // maximum number of instances to create + recurrenceRuleString += `${recurrenceRuleData.count}`; + } + + if (recurrenceRuleData.weekdays?.length) { + recurrenceRuleString += ";BYDAY="; + + // add the days of the week the event would recur + for (const weekDay of recurrenceRuleData.weekdays) { + recurrenceRuleString += `${weekDay},`; + } + + recurrenceRuleString = recurrenceRuleString.slice(0, -1); + } + + return recurrenceRuleString; +}; diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index 0321b44b28..4c29e0433b 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -4,8 +4,22 @@ import type { InterfaceEvent } from "../../../models"; import { Event } from "../../../models"; import type { EventInput } from "../../../types/generatedGraphQLTypes"; +/** + * This function generates the recurring event instances. + * @param data - the EventInput data provided in the args. + * @param baseRecurringEventId - _id of the baseRecurringEvent. + * @param recurrenceRuleId - _id of the recurrenceRule document containing the recurrence rule that the instances follow. + * @param recurringInstanceDates - the dates of the recurring instances. + * @param currentUserId - _id of the current user. + * @param organizationId - _id of the current organization. + * @remarks The following steps are followed: + * 1. Generate the instances for each provided date. + * 2. Insert the documents in the database. + * @returns The recurring instances generated during this operation. + */ + interface InterfaceGenerateRecurringInstances { - eventData: EventInput; + data: EventInput; baseRecurringEventId: string; recurrenceRuleId: string; recurringInstanceDates: Date[]; @@ -24,7 +38,7 @@ interface InterfaceRecurringEvent extends EventInput { } export const generateRecurringEventInstances = async ({ - eventData, + data, baseRecurringEventId, recurrenceRuleId, recurringInstanceDates, @@ -37,7 +51,7 @@ export const generateRecurringEventInstances = async ({ const formattedInstanceDate = format(date, "yyyy-MM-dd"); const createdEventInstance = { - ...eventData, + ...data, startDate: formattedInstanceDate, endDate: formattedInstanceDate, recurring: true, diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts index a528c0f2d5..06ffcb103c 100644 --- a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -2,6 +2,19 @@ import { addMonths } from "date-fns"; import { rrulestr } from "rrule"; import { RECURRING_EVENT_INSTANCES_MONTH_LIMIT } from "../../../constants"; +/** + * This function returns the dates for the recurring event instances. + * @param recurrenceRuleString - the rrule string for the recurrenceRule. + * @param recurrenceStartDate - the starting date from which we want to generate instances. + * @param eventEndDate - the end date of the event + * @param calendarDate - the last date of the current calendar month (To be used during query). + * @remarks The following steps are followed: + * 1. Limit the end date for instance creation. + * 2. Getting the date upto which we would generate instances during this operation (leaving the rest for dynamic generation). + * 3. Getting the dates for recurring instances. + * @returns The recurring instance dates and the date of last instance generated during this operation. + */ + export function getRecurringInstanceDates( recurrenceRuleString: string, recurrenceStartDate: Date, @@ -11,9 +24,10 @@ export function getRecurringInstanceDates( const limitEndDate = addMonths( calendarDate, RECURRING_EVENT_INSTANCES_MONTH_LIMIT - // generate instances upto these many months ahead + // generate instances upto this many months ahead // leave the rest for dynamic generation during queries ); + eventEndDate = eventEndDate || limitEndDate; const generateUptoDate = new Date( diff --git a/src/resolvers/Mutation/createEvent.ts b/src/resolvers/Mutation/createEvent.ts index bbcc370b52..9fd4cdc1bf 100644 --- a/src/resolvers/Mutation/createEvent.ts +++ b/src/resolvers/Mutation/createEvent.ts @@ -10,12 +10,10 @@ import { } from "../../constants"; import { isValidString } from "../../libraries/validators/validateString"; import { compareDates } from "../../libraries/validators/compareDates"; -import { cacheEvents } from "../../services/EventCache/cacheEvents"; import { session } from "../../db"; import { createSingleEvent, createRecurringEvents, - associateEventWithUser, } from "../../helpers/event/createEventHelpers"; /** @@ -135,30 +133,15 @@ export const createEvent: MutationResolvers["createEvent"] = async ( try { let createdEvent: InterfaceEvent; - const { data } = args; - if (args.data.recurring) { - // generate recurring events upto a date limit, - // leave the rest for the query - // create recurring event instances const recurringEventInstances = await createRecurringEvents( - data, + args, currentUser?._id.toString(), organization?._id.toString(), session ); - // associate recurring event instances with the current user and cache them - for (const recurringEventInstance of recurringEventInstances) { - await associateEventWithUser( - currentUser, - recurringEventInstance, - session - ); - await cacheEvents([recurringEventInstance]); - } - createdEvent = recurringEventInstances[0]; } else { // create a single non-recurring event @@ -168,10 +151,6 @@ export const createEvent: MutationResolvers["createEvent"] = async ( organization?._id.toString(), session ); - - // associate event with the current user and cache it - await associateEventWithUser(currentUser, createdEvent, session); - await cacheEvents([createdEvent]); } if (session) { diff --git a/src/typeDefs/inputs.ts b/src/typeDefs/inputs.ts index 1fda1a1c65..78a81fdf73 100644 --- a/src/typeDefs/inputs.ts +++ b/src/typeDefs/inputs.ts @@ -70,7 +70,6 @@ export const inputs = gql` startDate: Date! endDate: Date startTime: Time - recurrenceRuleString: String endTime: Time allDay: Boolean! recurring: Boolean! diff --git a/src/typeDefs/mutations.ts b/src/typeDefs/mutations.ts index cdd6dd4bd5..4c12ceefe7 100644 --- a/src/typeDefs/mutations.ts +++ b/src/typeDefs/mutations.ts @@ -79,8 +79,10 @@ export const mutations = gql` nameOfOrg: String! ): Donation! - createEvent(data: EventInput!, recurrenceRule: RecurrenceRuleInput): Event! - @auth + createEvent( + data: EventInput! + recurrenceRuleData: RecurrenceRuleInput + ): Event! @auth createGroupChat(data: createGroupChatInput!): GroupChat! @auth diff --git a/src/types/generatedGraphQLTypes.ts b/src/types/generatedGraphQLTypes.ts index 0c2f1598ba..2914fa9160 100644 --- a/src/types/generatedGraphQLTypes.ts +++ b/src/types/generatedGraphQLTypes.ts @@ -347,7 +347,6 @@ export type EventInput = { longitude?: InputMaybe; organizationId: Scalars['ID']['input']; recurrance?: InputMaybe; - recurrenceRuleString?: InputMaybe; recurring: Scalars['Boolean']['input']; startDate: Scalars['Date']['input']; startTime?: InputMaybe; @@ -821,7 +820,7 @@ export type MutationCreateDonationArgs = { export type MutationCreateEventArgs = { data: EventInput; - recurrenceRule?: InputMaybe; + recurrenceRuleData?: InputMaybe; }; From d80d6ad77688aa73c2360f8659f7430944d3152c Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 7 Feb 2024 17:29:06 +0530 Subject: [PATCH 04/29] add tests --- .../createRecurringEvents.ts | 10 +++ .../createEventHelpers/createSingleEvent.ts | 6 ++ .../generateRecurrenceRuleString.ts | 2 +- .../generateRecurringEventInstances.ts | 6 +- src/models/RecurrenceRule.ts | 32 ++++++-- tests/resolvers/Mutation/createEvent.spec.ts | 73 ++++++++++++++----- 6 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts index 0f642123d8..76f217e26b 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -10,6 +10,7 @@ import { import { generateRecurrenceRuleString } from "../recurringEventHelpers/generateRecurrenceRuleString"; import { associateEventWithUser } from "./associateEventWithUser"; import { cacheEvents } from "../../../services/EventCache/cacheEvents"; +import { format } from "date-fns"; /** * This function create the instances of a recurring event upto a certain date. @@ -37,6 +38,7 @@ export const createRecurringEvents = async ( let { recurrenceRuleData } = args; if (!recurrenceRuleData) { + // create a default weekly recurrence rule recurrenceRuleData = { frequency: "WEEKLY", }; @@ -48,12 +50,20 @@ export const createRecurringEvents = async ( data?.endDate ); + const formattedStartDate = format(data.startDate, "yyyy-MM-dd"); + let formattedEndDate = undefined; + if (data.endDate) { + formattedEndDate = format(data.endDate, "yyyy-MM-dd"); + } + // create a base recurring event first, based on which all the // recurring instances would be dynamically generated const baseRecurringEvent = await Event.create( [ { ...data, + startDate: formattedStartDate, + endDate: formattedEndDate, recurring: true, isBaseRecurringEvent: true, creatorId: currentUserId, diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index 10173e9a87..296afc80df 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -4,6 +4,7 @@ import { Event } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; import { associateEventWithUser } from "./associateEventWithUser"; import { cacheEvents } from "../../../services/EventCache/cacheEvents"; +import { format } from "date-fns"; /** * This function generates a single non-recurring event. @@ -22,11 +23,16 @@ export const createSingleEvent = async ( organizationId: string, session: mongoose.ClientSession ): Promise> => { + const formattedStartDate = format(args.data?.startDate, "yyyy-MM-dd"); + const formattedEndDate = format(args.data?.endDate, "yyyy-MM-dd"); + // create the single event const createdEvent = await Event.create( [ { ...args.data, + startDate: formattedStartDate, + endDate: formattedEndDate, creatorId: currentUserId, admins: [currentUserId], organization: organizationId, diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index 62dfb76c40..fc21a7116f 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -57,7 +57,7 @@ export const generateRecurrenceRuleString = ( recurrenceRuleString += `${recurrenceRuleData.count}`; } - if (recurrenceRuleData.weekdays?.length) { + if (recurrenceRuleData.weekdays && recurrenceRuleData.weekdays?.length) { recurrenceRuleString += ";BYDAY="; // add the days of the week the event would recur diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index 4c29e0433b..a4099741ed 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -67,13 +67,9 @@ export const generateRecurringEventInstances = async ({ }); //Bulk insertion in database - let recurringEventInstances = await Event.insertMany(recurringInstances, { + const recurringEventInstances = await Event.insertMany(recurringInstances, { session, }); - recurringEventInstances = Array.isArray(recurringEventInstances) - ? recurringEventInstances - : [recurringEventInstances]; - return recurringEventInstances; }; diff --git a/src/models/RecurrenceRule.ts b/src/models/RecurrenceRule.ts index b84ea8e3e9..86ce48a621 100644 --- a/src/models/RecurrenceRule.ts +++ b/src/models/RecurrenceRule.ts @@ -6,6 +6,23 @@ import type { InterfaceEvent } from "./Event"; * This is an interface representing a document for a recurrence rule in the database(MongoDB). */ +export enum Frequency { + YEARLY = "YEARLY", + MONTHLY = "MONTHLY", + WEEKLY = "WEEKLY", + DAILY = "DAILY", +} + +export enum WeekDays { + SUNDAY = "SUNDAY", + MONDAY = "MONDAY", + TUESDAY = "TUESDAY", + WEDNESDAY = "WEDNESDAY", + THURSDAY = "THURSDAY", + FRIDAY = "FRIDAY", + SATURDAY = "SATURDAY", +} + export interface InterfaceRecurrenceRule { _id: Types.ObjectId; organizationId: Types.ObjectId; @@ -13,9 +30,9 @@ export interface InterfaceRecurrenceRule { recurrenceRuleString: string; startDate: Date; endDate: Date; - frequency: "YEARLY" | "MONTHLY" | "WEEKLY" | "DAILY"; + frequency: Frequency; count: number; - weekDays: string[]; + weekDays: WeekDays[]; latestInstanceDate: Date; } @@ -54,22 +71,21 @@ const recurrenceRuleSchema = new Schema( }, endDate: { type: Date, + required: false, }, frequency: { type: String, required: true, - enum: ["YEARLY", "MONTHLY", "WEEKLY", "DAILY"], + enum: Object.values(Frequency), }, count: { type: Number, + required: false, }, - weekDays: [ - { - type: String, - }, - ], + weekDays: { type: [String], required: true, enum: Object.values(WeekDays) }, latestInstanceDate: { type: Date, + required: true, }, }, { diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 42cb99f4cf..e82d96ef35 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -23,6 +23,8 @@ import { UnauthorizedError, } from "../../../src/libraries/errors"; import { fail } from "assert"; +import { addMonths, format } from "date-fns"; +import { Frequency, RecurrenceRule } from "../../../src/models/RecurrenceRule"; let testUser: TestUserType; let testOrganization: TestOrganizationType; let MONGOOSE_INSTANCE: typeof mongoose; @@ -274,7 +276,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); }); - it(`creates the single recurring event and returns it`, async () => { + it(`creates default Weekly recurring instances if the recurrenceRuleData is not provided`, async () => { await User.updateOne( { _id: testUser?._id, @@ -287,23 +289,26 @@ describe("resolvers -> Mutation -> createEvent", () => { } ); + const startDate = new Date(); + const endDate = addMonths(startDate, 1); + const args: MutationCreateEventArgs = { data: { organizationId: testOrganization?.id, allDay: false, description: "newDescription", - endDate: new Date("2023-01-29T00:00:00Z"), - endTime: new Date().toUTCString(), + endDate, + endTime: endDate.toUTCString(), isPublic: false, isRegisterable: false, latitude: 1, longitude: 1, location: "newLocation", recurring: true, - startDate: new Date("2023-01-02T00:00:00Z"), - startTime: new Date().toUTCString(), + startDate, + startTime: startDate.toUTCString(), title: "newTitle", - recurrance: "ONCE", + recurrance: "WEEKLY", }, }; @@ -333,14 +338,26 @@ describe("resolvers -> Mutation -> createEvent", () => { }) ); + const recurrenceRule = await RecurrenceRule.findOne({ + startDate, + endDate, + frequency: Frequency.WEEKLY, + }); + + const baseRecurringEvent = await Event.findOne({ + isBaseRecurringEvent: true, + startDate: format(startDate, "yyyy-MM-dd"), + }); + const recurringEvents = await Event.find({ recurring: true, - recurrance: "ONCE", - startDate: "2023-01-02T00:00:00Z", + isBaseRecurringEvent: false, + recurrenceRuleId: recurrenceRule?._id, + baseRecurringEventId: baseRecurringEvent?._id, }).lean(); expect(recurringEvents).toBeDefined(); - expect(recurringEvents).toHaveLength(1); + expect(recurringEvents.length).toBeGreaterThan(1); const attendeeExists = await EventAttendee.exists({ userId: testUser?._id, @@ -364,7 +381,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); }); - it(`creates the Weekly recurring event and returns it`, async () => { + it(`creates the recurring event based on the recurrenceRuleData`, async () => { await User.updateOne( { _id: testUser?._id, @@ -377,23 +394,29 @@ describe("resolvers -> Mutation -> createEvent", () => { } ); + let startDate = new Date(); + startDate = addMonths(startDate, 1); + const args: MutationCreateEventArgs = { data: { organizationId: testOrganization?.id, - allDay: false, + allDay: true, description: "newDescription", - endDate: new Date("2023-01-29T00:00:00Z"), - endTime: new Date().toUTCString(), isPublic: false, isRegisterable: false, latitude: 1, longitude: 1, location: "newLocation", recurring: true, - startDate: new Date("2023-01-01T00:00:00Z"), - startTime: new Date().toUTCString(), + startDate, + startTime: startDate.toUTCString(), title: "newTitle", - recurrance: "WEEKLY", + recurrance: "ONCE", + }, + recurrenceRuleData: { + frequency: "WEEKLY", + weekdays: ["TH", "SA"], + count: 10, }, }; @@ -408,7 +431,7 @@ describe("resolvers -> Mutation -> createEvent", () => { expect(createEventPayload).toEqual( expect.objectContaining({ - allDay: false, + allDay: true, description: "newDescription", isPublic: false, isRegisterable: false, @@ -423,13 +446,25 @@ describe("resolvers -> Mutation -> createEvent", () => { }) ); + const recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.WEEKLY, + startDate, + }); + + const baseRecurringEvent = await Event.findOne({ + isBaseRecurringEvent: true, + startDate: format(startDate, "yyyy-MM-dd"), + }); + const recurringEvents = await Event.find({ recurring: true, - recurrance: "WEEKLY", + isBaseRecurringEvent: false, + recurrenceRuleId: recurrenceRule?._id, + baseRecurringEventId: baseRecurringEvent?._id, }).lean(); expect(recurringEvents).toBeDefined(); - expect(recurringEvents).toHaveLength(5); + expect(recurringEvents).toHaveLength(10); const attendeeExists = await EventAttendee.exists({ userId: testUser?._id, From 9ae17547d5a56f2f931792d7183c6faeb13df4cc Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 7 Feb 2024 21:32:28 +0530 Subject: [PATCH 05/29] fix comments --- docker-compose.dev.yaml | 18 ++++++++++++++++++ .../createRecurringEvents.ts | 14 +++++++------- .../createRecurrenceRule.ts | 6 +++--- .../generateRecurrenceRuleString.ts | 8 ++++---- .../getRecurringInstanceDates.ts | 10 +++++----- 5 files changed, 37 insertions(+), 19 deletions(-) create mode 100644 docker-compose.dev.yaml diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000000..85a01eabc1 --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,18 @@ +services: + mongodb: + image: mongo:latest + ports: + - 27017:27017 + volumes: + - mongodb-data:/data/db + + redis-stack-server: + image: redis/redis-stack-server:latest + ports: + - 6379:6379 + volumes: + - redis-data:/data/redis + +volumes: + mongodb-data: + redis-data: diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts index 76f217e26b..bde3ef0fb3 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -13,7 +13,7 @@ import { cacheEvents } from "../../../services/EventCache/cacheEvents"; import { format } from "date-fns"; /** - * This function create the instances of a recurring event upto a certain date. + * This function creates the instances of a recurring event upto a certain date. * @param args - payload of the createEvent mutation * @param currentUserId - _id of the current user * @param organizationId - _id of the organization the events belongs to @@ -32,7 +32,7 @@ export const createRecurringEvents = async ( args: MutationCreateEventArgs, currentUserId: string, organizationId: string, - session: mongoose.ClientSession + session: mongoose.ClientSession, ): Promise => { const { data } = args; let { recurrenceRuleData } = args; @@ -47,7 +47,7 @@ export const createRecurringEvents = async ( const recurrenceRuleString = generateRecurrenceRuleString( recurrenceRuleData, data?.startDate, - data?.endDate + data?.endDate, ); const formattedStartDate = format(data.startDate, "yyyy-MM-dd"); @@ -71,7 +71,7 @@ export const createRecurringEvents = async ( organization: organizationId, }, ], - { session } + { session }, ); // get the dates for the recurringInstances, and the date of the last instance @@ -80,7 +80,7 @@ export const createRecurringEvents = async ( getRecurringInstanceDates( recurrenceRuleString, data.startDate, - data.endDate + data.endDate, ); // create a recurrenceRule document that would contain the recurrence pattern @@ -91,7 +91,7 @@ export const createRecurringEvents = async ( organizationId.toString(), baseRecurringEvent[0]?._id.toString(), latestInstanceDate, - session + session, ); // generate the recurring instances @@ -110,7 +110,7 @@ export const createRecurringEvents = async ( await associateEventWithUser( currentUserId, recurringEventInstance?._id.toString(), - session + session, ); await cacheEvents([recurringEventInstance]); } diff --git a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts index 3f4cccc220..754c3d5d61 100644 --- a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts +++ b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts @@ -9,7 +9,7 @@ import { import { format } from "date-fns"; /** - * This function generates the recurring event instances. + * 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. @@ -30,7 +30,7 @@ export const createRecurrenceRule = async ( organizationId: string, baseRecurringEventId: string, latestInstanceDate: Date, - session: mongoose.ClientSession + session: mongoose.ClientSession, ): Promise => { const recurrenceRuleObject = rrulestr(recurrenceRuleString as string); @@ -56,7 +56,7 @@ export const createRecurrenceRule = async ( latestInstanceDate: formattedLatestInstanceDate, }, ], - { session } + { session }, ); return recurrenceRule[0].toObject(); diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index fc21a7116f..091538d40b 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -2,7 +2,7 @@ import { format } from "date-fns"; import type { RecurrenceRuleInput } from "../../../types/generatedGraphQLTypes"; /** - * This function generates the recurring event instances. + * This function generates the recurrence rule (rrule) string. * @param recurrenceRuleData - the recurrenceRuleInput provided in the args. * @param recurrenceStartDate - start date of recurrence. * @param recurrenceEndDate - end date of recurrence. @@ -15,14 +15,14 @@ import type { RecurrenceRuleInput } from "../../../types/generatedGraphQLTypes"; export const generateRecurrenceRuleString = ( recurrenceRuleData: RecurrenceRuleInput, recurrenceStartDate: Date, - recurrenceEndDate?: Date + recurrenceEndDate?: Date, ): string => { // Initiate an empty recurrenceRule string let recurrenceRuleString = ""; const formattedRecurrenceStartDate = format( recurrenceStartDate, - "yyyyMMdd'T'HHmmss'Z'" + "yyyyMMdd'T'HHmmss'Z'", ); recurrenceRuleString += "DTSTART:"; @@ -41,7 +41,7 @@ export const generateRecurrenceRuleString = ( if (recurrenceEndDate) { const formattedRecurrenceEndDate = format( recurrenceEndDate, - "yyyyMMdd'T'HHmmss'Z'" + "yyyyMMdd'T'HHmmss'Z'", ); recurrenceRuleString += ";UNTIL="; diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts index 06ffcb103c..18cfe1002f 100644 --- a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -3,7 +3,7 @@ import { rrulestr } from "rrule"; import { RECURRING_EVENT_INSTANCES_MONTH_LIMIT } from "../../../constants"; /** - * This function returns the dates for the recurring event instances. + * This function generates the dates of recurrence for the recurring event. * @param recurrenceRuleString - the rrule string for the recurrenceRule. * @param recurrenceStartDate - the starting date from which we want to generate instances. * @param eventEndDate - the end date of the event @@ -19,11 +19,11 @@ export function getRecurringInstanceDates( recurrenceRuleString: string, recurrenceStartDate: Date, eventEndDate: Date | null, - calendarDate: Date = recurrenceStartDate + calendarDate: Date = recurrenceStartDate, ): [Date[], Date] { const limitEndDate = addMonths( calendarDate, - RECURRING_EVENT_INSTANCES_MONTH_LIMIT + RECURRING_EVENT_INSTANCES_MONTH_LIMIT, // generate instances upto this many months ahead // leave the rest for dynamic generation during queries ); @@ -31,7 +31,7 @@ export function getRecurringInstanceDates( eventEndDate = eventEndDate || limitEndDate; const generateUptoDate = new Date( - Math.min(eventEndDate.getTime(), limitEndDate.getTime()) + Math.min(eventEndDate.getTime(), limitEndDate.getTime()), ); const recurrenceRuleObject = rrulestr(recurrenceRuleString); @@ -39,7 +39,7 @@ export function getRecurringInstanceDates( const recurringInstanceDates = recurrenceRuleObject.between( recurrenceStartDate, generateUptoDate, - true + true, ); const latestInstanceDate = From 5afad7c47b2909b1aacc2350db438736d38b8535 Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 7 Feb 2024 21:47:38 +0530 Subject: [PATCH 06/29] update generateRecurrenceRuleString function --- docker-compose.dev.yaml | 18 ------ .../generateRecurrenceRuleString.ts | 55 ++++++------------- 2 files changed, 18 insertions(+), 55 deletions(-) delete mode 100644 docker-compose.dev.yaml diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml deleted file mode 100644 index 85a01eabc1..0000000000 --- a/docker-compose.dev.yaml +++ /dev/null @@ -1,18 +0,0 @@ -services: - mongodb: - image: mongo:latest - ports: - - 27017:27017 - volumes: - - mongodb-data:/data/db - - redis-stack-server: - image: redis/redis-stack-server:latest - ports: - - 6379:6379 - volumes: - - redis-data:/data/redis - -volumes: - mongodb-data: - redis-data: diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index 091538d40b..fae40570f2 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -17,55 +17,36 @@ export const generateRecurrenceRuleString = ( recurrenceStartDate: Date, recurrenceEndDate?: Date, ): string => { - // Initiate an empty recurrenceRule string - let recurrenceRuleString = ""; + // destructure the rules + const { frequency, count, weekdays } = recurrenceRuleData; + // recurrence start date + // (not necessarily the start date of the first recurring instance) const formattedRecurrenceStartDate = format( recurrenceStartDate, "yyyyMMdd'T'HHmmss'Z'", ); - recurrenceRuleString += "DTSTART:"; - // recurrence start date - // (not necessarily the start date of the first recurring instance) - recurrenceRuleString += `${formattedRecurrenceStartDate}\n`; - - // add recurrence rules one by one - recurrenceRuleString += "RRULE:"; - - // frequency of recurrence - // (defaulting to "WEEKLY" if recurrenceRule is not provided) - recurrenceRuleString += "FREQ="; - recurrenceRuleString += `${recurrenceRuleData.frequency}`; + // date upto which instances would be generated + const formattedRecurrenceEndDate = recurrenceEndDate + ? format(recurrenceEndDate, "yyyyMMdd'T'HHmmss'Z'") + : ""; - if (recurrenceEndDate) { - const formattedRecurrenceEndDate = format( - recurrenceEndDate, - "yyyyMMdd'T'HHmmss'Z'", - ); + // string representing the days of the week the event would recur + const weekdaysString = weekdays?.length ? weekdays.join(",") : ""; - recurrenceRuleString += ";UNTIL="; + // initiate recurrence rule string + let recurrenceRuleString = `DTSTART:${formattedRecurrenceStartDate}\nRRULE:FREQ=${frequency}`; - // date upto which instances would be generated - recurrenceRuleString += `${formattedRecurrenceEndDate}`; + if (formattedRecurrenceEndDate) { + recurrenceRuleString += `;UNTIL=${formattedRecurrenceEndDate}`; } - - if (recurrenceRuleData.count) { - recurrenceRuleString += ";COUNT="; - + if (count) { // maximum number of instances to create - recurrenceRuleString += `${recurrenceRuleData.count}`; + recurrenceRuleString += `;COUNT=${count}`; } - - if (recurrenceRuleData.weekdays && recurrenceRuleData.weekdays?.length) { - recurrenceRuleString += ";BYDAY="; - - // add the days of the week the event would recur - for (const weekDay of recurrenceRuleData.weekdays) { - recurrenceRuleString += `${weekDay},`; - } - - recurrenceRuleString = recurrenceRuleString.slice(0, -1); + if (weekdaysString) { + recurrenceRuleString += `;BYDAY=${weekdaysString}`; } return recurrenceRuleString; From 694dd5c0f129af65fc1997f9873bc706ae184d86 Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 12:36:27 +0530 Subject: [PATCH 07/29] speed up code --- .../associateEventWithUser.ts | 43 +++++++++--------- .../createRecurringEvents.ts | 22 +++++---- .../createRecurrenceRule.ts | 12 +++-- src/models/Event.ts | 12 ++--- tests/resolvers/Mutation/createEvent.spec.ts | 45 ++++++++++--------- 5 files changed, 70 insertions(+), 64 deletions(-) diff --git a/src/helpers/event/createEventHelpers/associateEventWithUser.ts b/src/helpers/event/createEventHelpers/associateEventWithUser.ts index b4efbed105..0100b6658f 100644 --- a/src/helpers/event/createEventHelpers/associateEventWithUser.ts +++ b/src/helpers/event/createEventHelpers/associateEventWithUser.ts @@ -13,29 +13,30 @@ import { EventAttendee, User } from "../../../models"; export const associateEventWithUser = async ( currentUserId: string, createdEventId: string, - session: mongoose.ClientSession + session: mongoose.ClientSession, ): Promise => { - await EventAttendee.create( - [ + await Promise.all([ + EventAttendee.create( + [ + { + userId: currentUserId, + eventId: createdEventId, + }, + ], + { session }, + ), + User.updateOne( { - userId: currentUserId, - eventId: createdEventId, + _id: currentUserId, }, - ], - { session } - ); - - await User.updateOne( - { - _id: currentUserId, - }, - { - $push: { - eventAdmin: createdEventId, - createdEvents: createdEventId, - registeredEvents: createdEventId, + { + $push: { + eventAdmin: createdEventId, + createdEvents: createdEventId, + registeredEvents: createdEventId, + }, }, - }, - { session } - ); + { session }, + ), + ]); }; diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts index bde3ef0fb3..6b3a03df46 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -106,14 +106,20 @@ export const createRecurringEvents = async ( }); // associate the instances with the user and cache them - for (const recurringEventInstance of recurringEventInstances) { - await associateEventWithUser( - currentUserId, - recurringEventInstance?._id.toString(), - session, - ); - await cacheEvents([recurringEventInstance]); - } + const associateAndCacheInstances = recurringEventInstances.map( + async (recurringEventInstance) => { + await Promise.all([ + associateEventWithUser( + currentUserId, + recurringEventInstance?._id.toString(), + session, + ), + cacheEvents([recurringEventInstance]), + ]); + }, + ); + + await Promise.all(associateAndCacheInstances); return recurringEventInstances; }; diff --git a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts index 754c3d5d61..f8e93a06df 100644 --- a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts +++ b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts @@ -32,15 +32,19 @@ export const createRecurrenceRule = async ( latestInstanceDate: Date, session: mongoose.ClientSession, ): Promise => { - const recurrenceRuleObject = rrulestr(recurrenceRuleString as string); + const recurrenceRuleObject = rrulestr(recurrenceRuleString); + + const { freq, byweekday } = recurrenceRuleObject.options; const weekDays: string[] = []; - for (const weekday of recurrenceRuleObject.options.byweekday) { - weekDays.push(RECURRENCE_WEEKDAYS[weekday]); + if (byweekday) { + for (const weekday of byweekday) { + weekDays.push(RECURRENCE_WEEKDAYS[weekday]); + } } const formattedLatestInstanceDate = format(latestInstanceDate, "yyyy-MM-dd"); - const frequency = RECURRENCE_FREQUENCIES[recurrenceRuleObject.options.freq]; + const frequency = RECURRENCE_FREQUENCIES[freq]; const recurrenceRule = await RecurrenceRule.create( [ diff --git a/src/models/Event.ts b/src/models/Event.ts index 03920b7a4e..a69525ae42 100644 --- a/src/models/Event.ts +++ b/src/models/Event.ts @@ -108,18 +108,12 @@ const eventSchema = new Schema( recurrenceRuleId: { type: Schema.Types.ObjectId, ref: "RecurrenceRule", - required: function (): () => boolean { - // @ts-expect-error Suppressing typescript error for conditional required field - return this.recurring && !this.isBaseRecurringEvent; - }, + required: false, }, baseRecurringEventId: { type: Schema.Types.ObjectId, ref: "Event", - required: function (): () => boolean { - // @ts-expect-error Suppressing typescript error for conditional required field - return this.recurring && !this.isBaseRecurringEvent; - }, + required: false, }, allDay: { type: Boolean, @@ -193,7 +187,7 @@ const eventSchema = new Schema( }, { timestamps: true, - } + }, ); const eventModel = (): Model => diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index e82d96ef35..c011e14730 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -51,11 +51,11 @@ beforeAll(async () => { $push: { adminFor: testOrganization?._id, }, - } + }, ); const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message + (message) => message, ); }); @@ -179,7 +179,7 @@ describe("resolvers -> Mutation -> createEvent", () => { } catch (error: unknown) { if (error instanceof UnauthorizedError) { expect(error.message).toEqual( - ORGANIZATION_NOT_AUTHORIZED_ERROR.MESSAGE + ORGANIZATION_NOT_AUTHORIZED_ERROR.MESSAGE, ); } else { fail(`Expected UnauthorizedError, but got ${error}`); @@ -197,7 +197,7 @@ describe("resolvers -> Mutation -> createEvent", () => { createdOrganizations: testOrganization?._id, joinedOrganizations: testOrganization?._id, }, - } + }, ); const args: MutationCreateEventArgs = { @@ -215,7 +215,7 @@ describe("resolvers -> Mutation -> createEvent", () => { recurring: false, startDate: new Date("2023-01-01T00:00:00Z"), startTime: new Date().toUTCString(), - title: "newTitle", + title: "singleEventTitle", recurrance: "ONCE", }, }; @@ -239,14 +239,15 @@ describe("resolvers -> Mutation -> createEvent", () => { longitude: 1, location: "newLocation", recurring: false, - title: "newTitle", + title: "singleEventTitle", creatorId: testUser?._id, admins: expect.arrayContaining([testUser?._id]), organization: testOrganization?._id, - }) + }), ); const recurringEvents = await Event.find({ + title: "singleEventTitle", recurring: false, recurrance: "ONCE", }).lean(); @@ -272,7 +273,7 @@ describe("resolvers -> Mutation -> createEvent", () => { eventAdmin: expect.arrayContaining([createEventPayload?._id]), createdEvents: expect.arrayContaining([createEventPayload?._id]), registeredEvents: expect.arrayContaining([createEventPayload?._id]), - }) + }), ); }); @@ -286,7 +287,7 @@ describe("resolvers -> Mutation -> createEvent", () => { createdOrganizations: testOrganization?._id, joinedOrganizations: testOrganization?._id, }, - } + }, ); const startDate = new Date(); @@ -335,7 +336,7 @@ describe("resolvers -> Mutation -> createEvent", () => { creatorId: testUser?._id, admins: expect.arrayContaining([testUser?._id]), organization: testOrganization?._id, - }) + }), ); const recurrenceRule = await RecurrenceRule.findOne({ @@ -377,7 +378,7 @@ describe("resolvers -> Mutation -> createEvent", () => { eventAdmin: expect.arrayContaining([createEventPayload?._id]), createdEvents: expect.arrayContaining([createEventPayload?._id]), registeredEvents: expect.arrayContaining([createEventPayload?._id]), - }) + }), ); }); @@ -391,7 +392,7 @@ describe("resolvers -> Mutation -> createEvent", () => { createdOrganizations: testOrganization?._id, joinedOrganizations: testOrganization?._id, }, - } + }, ); let startDate = new Date(); @@ -443,7 +444,7 @@ describe("resolvers -> Mutation -> createEvent", () => { creatorId: testUser?._id, admins: expect.arrayContaining([testUser?._id]), organization: testOrganization?._id, - }) + }), ); const recurrenceRule = await RecurrenceRule.findOne({ @@ -484,7 +485,7 @@ describe("resolvers -> Mutation -> createEvent", () => { eventAdmin: expect.arrayContaining([createEventPayload?._id]), createdEvents: expect.arrayContaining([createEventPayload?._id]), registeredEvents: expect.arrayContaining([createEventPayload?._id]), - }) + }), ); }); @@ -549,7 +550,7 @@ describe("Check for validation conditions", () => { it(`throws String Length Validation error if title is greater than 256 characters`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message + (message) => message, ); try { const args: MutationCreateEventArgs = { @@ -585,7 +586,7 @@ describe("Check for validation conditions", () => { } catch (error: unknown) { if (error instanceof InputValidationError) { expect(error.message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title` + `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, ); } else { fail(`Expected LengthValidationError, but got ${error}`); @@ -595,7 +596,7 @@ describe("Check for validation conditions", () => { it(`throws String Length Validation error if description is greater than 500 characters`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message + (message) => message, ); try { const args: MutationCreateEventArgs = { @@ -631,7 +632,7 @@ describe("Check for validation conditions", () => { } catch (error: unknown) { if (error instanceof InputValidationError) { expect(error.message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in description` + `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in description`, ); } else { fail(`Expected LengthValidationError, but got ${error}`); @@ -641,7 +642,7 @@ describe("Check for validation conditions", () => { it(`throws String Length Validation error if location is greater than 50 characters`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message + (message) => message, ); try { const args: MutationCreateEventArgs = { @@ -676,7 +677,7 @@ describe("Check for validation conditions", () => { } catch (error: unknown) { if (error instanceof InputValidationError) { expect(error.message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 50 characters in location` + `${LENGTH_VALIDATION_ERROR.MESSAGE} 50 characters in location`, ); } else { fail(`Expected LengthValidationError, but got ${error}`); @@ -686,7 +687,7 @@ describe("Check for validation conditions", () => { it(`throws Date Validation error if start date is greater than end date`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message + (message) => message, ); try { const args: MutationCreateEventArgs = { @@ -721,7 +722,7 @@ describe("Check for validation conditions", () => { } catch (error: unknown) { if (error instanceof InputValidationError) { expect(error.message).toEqual( - `start date must be earlier than end date` + `start date must be earlier than end date`, ); } else { fail(`Expected DateValidationError, but got ${error}`); From 1caf67f7d9de2552ec56084170983801e47203f3 Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 12:57:28 +0530 Subject: [PATCH 08/29] fix formatting --- README.md | 2 +- package.json | 2 +- .../associateEventWithUser.ts | 6 +-- .../createRecurringEvents.ts | 14 +++---- .../createRecurrenceRule.ts | 4 +- .../generateRecurrenceRuleString.ts | 4 +- .../getRecurringInstanceDates.ts | 8 ++-- src/models/Event.ts | 2 +- tests/resolvers/Mutation/createEvent.spec.ts | 40 +++++++++---------- 9 files changed, 41 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 200e9cffa8..bdacd57db5 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,4 @@ Core features include: ## Image Upload -To enable image upload functionalities create an images folder in the root of the project \ No newline at end of file +To enable image upload functionalities create an images folder in the root of the project diff --git a/package.json b/package.json index e027a988bb..52ba0e5746 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "lint:check": "eslint . --max-warnings=1500", "lint:fix": "eslint . --fix", "lint-staged": "lint-staged", - "format:fix": "prettier --write \"**/*.{ts,tsx,json,scss,css}\"", + "format:fix": "prettier --write", "format:check": "prettier --check \"**/*.{ts,tsx,json,scss,css}\"", "prepare": "husky install", "generate:graphql-markdown": "graphql-markdown http://localhost:4000/graphql > docs/Schema.md", diff --git a/src/helpers/event/createEventHelpers/associateEventWithUser.ts b/src/helpers/event/createEventHelpers/associateEventWithUser.ts index 0100b6658f..a165549155 100644 --- a/src/helpers/event/createEventHelpers/associateEventWithUser.ts +++ b/src/helpers/event/createEventHelpers/associateEventWithUser.ts @@ -13,7 +13,7 @@ import { EventAttendee, User } from "../../../models"; export const associateEventWithUser = async ( currentUserId: string, createdEventId: string, - session: mongoose.ClientSession, + session: mongoose.ClientSession ): Promise => { await Promise.all([ EventAttendee.create( @@ -23,7 +23,7 @@ export const associateEventWithUser = async ( eventId: createdEventId, }, ], - { session }, + { session } ), User.updateOne( { @@ -36,7 +36,7 @@ export const associateEventWithUser = async ( registeredEvents: createdEventId, }, }, - { session }, + { session } ), ]); }; diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts index 6b3a03df46..09eeff80a7 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -32,7 +32,7 @@ export const createRecurringEvents = async ( args: MutationCreateEventArgs, currentUserId: string, organizationId: string, - session: mongoose.ClientSession, + session: mongoose.ClientSession ): Promise => { const { data } = args; let { recurrenceRuleData } = args; @@ -47,7 +47,7 @@ export const createRecurringEvents = async ( const recurrenceRuleString = generateRecurrenceRuleString( recurrenceRuleData, data?.startDate, - data?.endDate, + data?.endDate ); const formattedStartDate = format(data.startDate, "yyyy-MM-dd"); @@ -71,7 +71,7 @@ export const createRecurringEvents = async ( organization: organizationId, }, ], - { session }, + { session } ); // get the dates for the recurringInstances, and the date of the last instance @@ -80,7 +80,7 @@ export const createRecurringEvents = async ( getRecurringInstanceDates( recurrenceRuleString, data.startDate, - data.endDate, + data.endDate ); // create a recurrenceRule document that would contain the recurrence pattern @@ -91,7 +91,7 @@ export const createRecurringEvents = async ( organizationId.toString(), baseRecurringEvent[0]?._id.toString(), latestInstanceDate, - session, + session ); // generate the recurring instances @@ -112,11 +112,11 @@ export const createRecurringEvents = async ( associateEventWithUser( currentUserId, recurringEventInstance?._id.toString(), - session, + session ), cacheEvents([recurringEventInstance]), ]); - }, + } ); await Promise.all(associateAndCacheInstances); diff --git a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts index f8e93a06df..d7afe2ce03 100644 --- a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts +++ b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts @@ -30,7 +30,7 @@ export const createRecurrenceRule = async ( organizationId: string, baseRecurringEventId: string, latestInstanceDate: Date, - session: mongoose.ClientSession, + session: mongoose.ClientSession ): Promise => { const recurrenceRuleObject = rrulestr(recurrenceRuleString); @@ -60,7 +60,7 @@ export const createRecurrenceRule = async ( latestInstanceDate: formattedLatestInstanceDate, }, ], - { session }, + { session } ); return recurrenceRule[0].toObject(); diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index fae40570f2..948b41bb05 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -15,7 +15,7 @@ import type { RecurrenceRuleInput } from "../../../types/generatedGraphQLTypes"; export const generateRecurrenceRuleString = ( recurrenceRuleData: RecurrenceRuleInput, recurrenceStartDate: Date, - recurrenceEndDate?: Date, + recurrenceEndDate?: Date ): string => { // destructure the rules const { frequency, count, weekdays } = recurrenceRuleData; @@ -24,7 +24,7 @@ export const generateRecurrenceRuleString = ( // (not necessarily the start date of the first recurring instance) const formattedRecurrenceStartDate = format( recurrenceStartDate, - "yyyyMMdd'T'HHmmss'Z'", + "yyyyMMdd'T'HHmmss'Z'" ); // date upto which instances would be generated diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts index 18cfe1002f..ba63bcccda 100644 --- a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -19,11 +19,11 @@ export function getRecurringInstanceDates( recurrenceRuleString: string, recurrenceStartDate: Date, eventEndDate: Date | null, - calendarDate: Date = recurrenceStartDate, + calendarDate: Date = recurrenceStartDate ): [Date[], Date] { const limitEndDate = addMonths( calendarDate, - RECURRING_EVENT_INSTANCES_MONTH_LIMIT, + RECURRING_EVENT_INSTANCES_MONTH_LIMIT // generate instances upto this many months ahead // leave the rest for dynamic generation during queries ); @@ -31,7 +31,7 @@ export function getRecurringInstanceDates( eventEndDate = eventEndDate || limitEndDate; const generateUptoDate = new Date( - Math.min(eventEndDate.getTime(), limitEndDate.getTime()), + Math.min(eventEndDate.getTime(), limitEndDate.getTime()) ); const recurrenceRuleObject = rrulestr(recurrenceRuleString); @@ -39,7 +39,7 @@ export function getRecurringInstanceDates( const recurringInstanceDates = recurrenceRuleObject.between( recurrenceStartDate, generateUptoDate, - true, + true ); const latestInstanceDate = diff --git a/src/models/Event.ts b/src/models/Event.ts index a69525ae42..6b073ebd19 100644 --- a/src/models/Event.ts +++ b/src/models/Event.ts @@ -187,7 +187,7 @@ const eventSchema = new Schema( }, { timestamps: true, - }, + } ); const eventModel = (): Model => diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index c011e14730..446b891d35 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -51,11 +51,11 @@ beforeAll(async () => { $push: { adminFor: testOrganization?._id, }, - }, + } ); const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, + (message) => message ); }); @@ -179,7 +179,7 @@ describe("resolvers -> Mutation -> createEvent", () => { } catch (error: unknown) { if (error instanceof UnauthorizedError) { expect(error.message).toEqual( - ORGANIZATION_NOT_AUTHORIZED_ERROR.MESSAGE, + ORGANIZATION_NOT_AUTHORIZED_ERROR.MESSAGE ); } else { fail(`Expected UnauthorizedError, but got ${error}`); @@ -197,7 +197,7 @@ describe("resolvers -> Mutation -> createEvent", () => { createdOrganizations: testOrganization?._id, joinedOrganizations: testOrganization?._id, }, - }, + } ); const args: MutationCreateEventArgs = { @@ -243,7 +243,7 @@ describe("resolvers -> Mutation -> createEvent", () => { creatorId: testUser?._id, admins: expect.arrayContaining([testUser?._id]), organization: testOrganization?._id, - }), + }) ); const recurringEvents = await Event.find({ @@ -273,7 +273,7 @@ describe("resolvers -> Mutation -> createEvent", () => { eventAdmin: expect.arrayContaining([createEventPayload?._id]), createdEvents: expect.arrayContaining([createEventPayload?._id]), registeredEvents: expect.arrayContaining([createEventPayload?._id]), - }), + }) ); }); @@ -287,7 +287,7 @@ describe("resolvers -> Mutation -> createEvent", () => { createdOrganizations: testOrganization?._id, joinedOrganizations: testOrganization?._id, }, - }, + } ); const startDate = new Date(); @@ -336,7 +336,7 @@ describe("resolvers -> Mutation -> createEvent", () => { creatorId: testUser?._id, admins: expect.arrayContaining([testUser?._id]), organization: testOrganization?._id, - }), + }) ); const recurrenceRule = await RecurrenceRule.findOne({ @@ -378,7 +378,7 @@ describe("resolvers -> Mutation -> createEvent", () => { eventAdmin: expect.arrayContaining([createEventPayload?._id]), createdEvents: expect.arrayContaining([createEventPayload?._id]), registeredEvents: expect.arrayContaining([createEventPayload?._id]), - }), + }) ); }); @@ -392,7 +392,7 @@ describe("resolvers -> Mutation -> createEvent", () => { createdOrganizations: testOrganization?._id, joinedOrganizations: testOrganization?._id, }, - }, + } ); let startDate = new Date(); @@ -444,7 +444,7 @@ describe("resolvers -> Mutation -> createEvent", () => { creatorId: testUser?._id, admins: expect.arrayContaining([testUser?._id]), organization: testOrganization?._id, - }), + }) ); const recurrenceRule = await RecurrenceRule.findOne({ @@ -485,7 +485,7 @@ describe("resolvers -> Mutation -> createEvent", () => { eventAdmin: expect.arrayContaining([createEventPayload?._id]), createdEvents: expect.arrayContaining([createEventPayload?._id]), registeredEvents: expect.arrayContaining([createEventPayload?._id]), - }), + }) ); }); @@ -550,7 +550,7 @@ describe("Check for validation conditions", () => { it(`throws String Length Validation error if title is greater than 256 characters`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, + (message) => message ); try { const args: MutationCreateEventArgs = { @@ -586,7 +586,7 @@ describe("Check for validation conditions", () => { } catch (error: unknown) { if (error instanceof InputValidationError) { expect(error.message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title`, + `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in title` ); } else { fail(`Expected LengthValidationError, but got ${error}`); @@ -596,7 +596,7 @@ describe("Check for validation conditions", () => { it(`throws String Length Validation error if description is greater than 500 characters`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, + (message) => message ); try { const args: MutationCreateEventArgs = { @@ -632,7 +632,7 @@ describe("Check for validation conditions", () => { } catch (error: unknown) { if (error instanceof InputValidationError) { expect(error.message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in description`, + `${LENGTH_VALIDATION_ERROR.MESSAGE} 500 characters in description` ); } else { fail(`Expected LengthValidationError, but got ${error}`); @@ -642,7 +642,7 @@ describe("Check for validation conditions", () => { it(`throws String Length Validation error if location is greater than 50 characters`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, + (message) => message ); try { const args: MutationCreateEventArgs = { @@ -677,7 +677,7 @@ describe("Check for validation conditions", () => { } catch (error: unknown) { if (error instanceof InputValidationError) { expect(error.message).toEqual( - `${LENGTH_VALIDATION_ERROR.MESSAGE} 50 characters in location`, + `${LENGTH_VALIDATION_ERROR.MESSAGE} 50 characters in location` ); } else { fail(`Expected LengthValidationError, but got ${error}`); @@ -687,7 +687,7 @@ describe("Check for validation conditions", () => { it(`throws Date Validation error if start date is greater than end date`, async () => { const { requestContext } = await import("../../../src/libraries"); vi.spyOn(requestContext, "translate").mockImplementation( - (message) => message, + (message) => message ); try { const args: MutationCreateEventArgs = { @@ -722,7 +722,7 @@ describe("Check for validation conditions", () => { } catch (error: unknown) { if (error instanceof InputValidationError) { expect(error.message).toEqual( - `start date must be earlier than end date`, + `start date must be earlier than end date` ); } else { fail(`Expected DateValidationError, but got ${error}`); From 8378739369ed0fb367d296c480218a4642636958 Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 12:58:49 +0530 Subject: [PATCH 09/29] format fix --- schema.graphql | 182 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 151 insertions(+), 31 deletions(-) diff --git a/schema.graphql b/schema.graphql index 3def5ece74..1a924e930a 100644 --- a/schema.graphql +++ b/schema.graphql @@ -523,9 +523,17 @@ type Mutation { addEventAttendee(data: EventAttendeeInput!): User! addFeedback(data: FeedbackInput!): Feedback! addLanguageTranslation(data: LanguageInput!): Language! - addOrganizationCustomField(name: String!, organizationId: ID!, type: String!): OrganizationCustomField! + addOrganizationCustomField( + name: String! + organizationId: ID! + type: String! + ): OrganizationCustomField! addOrganizationImage(file: String!, organizationId: String!): Organization! - addUserCustomData(dataName: String!, dataValue: Any!, organizationId: ID!): UserCustomData! + addUserCustomData( + dataName: String! + dataValue: Any! + organizationId: ID! + ): UserCustomData! addUserImage(file: String!): User! addUserToGroupChat(chatId: ID!, userId: ID!): GroupChat! addUserToUserFamily(familyId: ID!, userId: ID!): UserFamily! @@ -536,19 +544,47 @@ type Mutation { blockUser(organizationId: ID!, userId: ID!): User! cancelMembershipRequest(membershipRequestId: ID!): MembershipRequest! checkIn(data: CheckInInput!): CheckIn! - createActionItem(actionItemCategoryId: ID!, data: CreateActionItemInput!): ActionItem! - createActionItemCategory(name: String!, organizationId: ID!): ActionItemCategory! + createActionItem( + actionItemCategoryId: ID! + data: CreateActionItemInput! + ): ActionItem! + createActionItemCategory( + name: String! + organizationId: ID! + ): ActionItemCategory! createAdmin(data: UserAndOrganizationInput!): User! - createAdvertisement(endDate: Date!, link: String!, name: String!, orgId: ID!, startDate: Date!, type: String!): Advertisement! + createAdvertisement( + endDate: Date! + link: String! + name: String! + orgId: ID! + startDate: Date! + type: String! + ): Advertisement! createComment(data: CommentInput!, postId: ID!): Comment createDirectChat(data: createChatInput!): DirectChat! - createDonation(amount: Float!, nameOfOrg: String!, nameOfUser: String!, orgId: ID!, payPalId: ID!, userId: ID!): Donation! - createEvent(data: EventInput!, recurrenceRuleData: RecurrenceRuleInput): Event! + createDonation( + amount: Float! + nameOfOrg: String! + nameOfUser: String! + orgId: ID! + payPalId: ID! + userId: ID! + ): Donation! + createEvent( + data: EventInput! + recurrenceRuleData: RecurrenceRuleInput + ): Event! createGroupChat(data: createGroupChatInput!): GroupChat! createMember(input: UserAndOrganizationInput!): Organization! createMessageChat(data: MessageChatInput!): MessageChat! createOrganization(data: OrganizationInput, file: String): Organization! - createPlugin(pluginCreatedBy: String!, pluginDesc: String!, pluginName: String!, uninstalledOrgs: [ID!]): Plugin! + createPlugin( + pluginCreatedBy: String! + pluginDesc: String! + pluginName: String! + uninstalledOrgs: [ID!] + ): Plugin! createPost(data: PostInput!, file: String): Post createSampleOrganization: Boolean! createUserFamily(data: createUserFamilyInput!): UserFamily! @@ -578,7 +614,10 @@ type Mutation { removeGroupChat(chatId: ID!): GroupChat! removeMember(data: UserAndOrganizationInput!): Organization! removeOrganization(id: ID!): User! - removeOrganizationCustomField(customFieldId: ID!, organizationId: ID!): OrganizationCustomField! + removeOrganizationCustomField( + customFieldId: ID! + organizationId: ID! + ): OrganizationCustomField! removeOrganizationImage(organizationId: String!): Organization! removePost(id: ID!): Post removeSampleOrganization: Boolean! @@ -591,8 +630,14 @@ type Mutation { revokeRefreshTokenForUser: Boolean! saveFcmToken(token: String): Boolean! sendMembershipRequest(organizationId: ID!): MembershipRequest! - sendMessageToDirectChat(chatId: ID!, messageContent: String!): DirectChatMessage! - sendMessageToGroupChat(chatId: ID!, messageContent: String!): GroupChatMessage! + sendMessageToDirectChat( + chatId: ID! + messageContent: String! + ): DirectChatMessage! + sendMessageToGroupChat( + chatId: ID! + messageContent: String! + ): GroupChatMessage! signUp(data: UserInput!, file: String): AuthData! togglePostPin(id: ID!, title: String): Post! unassignUserTag(input: ToggleUserTagAssignInput!): User @@ -601,16 +646,29 @@ type Mutation { unlikePost(id: ID!): Post unregisterForEventByUser(id: ID!): Event! updateActionItem(data: UpdateActionItemInput!, id: ID!): ActionItem - updateActionItemCategory(data: UpdateActionItemCategoryInput!, id: ID!): ActionItemCategory - updateAdvertisement(input: UpdateAdvertisementInput!): UpdateAdvertisementPayload + updateActionItemCategory( + data: UpdateActionItemCategoryInput! + id: ID! + ): ActionItemCategory + updateAdvertisement( + input: UpdateAdvertisementInput! + ): UpdateAdvertisementPayload updateEvent(data: UpdateEventInput, id: ID!): Event! updateLanguage(languageCode: String!): User! - updateOrganization(data: UpdateOrganizationInput, file: String, id: ID!): Organization! + updateOrganization( + data: UpdateOrganizationInput + file: String + id: ID! + ): Organization! updatePluginStatus(id: ID!, orgId: ID!): Plugin! updatePost(data: PostUpdateInput, id: ID!): Post! updateUserPassword(data: UpdateUserPasswordInput!): User! updateUserProfile(data: UpdateUserInput, file: String): User! - updateUserRoleInOrganization(organizationId: ID!, role: String!, userId: ID!): Organization! + updateUserRoleInOrganization( + organizationId: ID! + role: String! + userId: ID! + ): Organization! updateUserTag(input: UpdateUserTagInput!): UserTag updateUserType(data: UpdateUserTypeInput!): Boolean! } @@ -637,7 +695,12 @@ type Organization { pinnedPosts: [Post] updatedAt: DateTime! userRegistrationRequired: Boolean! - userTags(after: String, before: String, first: PositiveInt, last: PositiveInt): UserTagsConnection + userTags( + after: String + before: String + first: PositiveInt + last: PositiveInt + ): UserTagsConnection visibleInSearch: Boolean! } @@ -716,14 +779,20 @@ type OtpData { otpToken: String! } -"""Information about pagination in a connection.""" +""" +Information about pagination in a connection. +""" type PageInfo { currPageNo: Int - """When paginating forwards, are there more items?""" + """ + When paginating forwards, are there more items? + """ hasNextPage: Boolean! - """When paginating backwards, are there more items?""" + """ + When paginating backwards, are there more items? + """ hasPreviousPage: Boolean! nextPageNo: Int prevPageNo: Int @@ -784,14 +853,20 @@ type Post { videoUrl: URL } -"""A connection to a list of items.""" +""" +A connection to a list of items. +""" type PostConnection { aggregate: AggregatePost! - """A list of edges.""" + """ + A list of edges. + """ edges: [Post]! - """Information to aid in pagination.""" + """ + Information to aid in pagination. + """ pageInfo: PageInfo! } @@ -866,11 +941,21 @@ type Query { directChatsMessagesByChatID(id: ID!): [DirectChatMessage] event(id: ID!): Event eventsByOrganization(id: ID, orderBy: EventOrderByInput): [Event] - eventsByOrganizationConnection(first: Int, orderBy: EventOrderByInput, skip: Int, where: EventWhereInput): [Event!]! + eventsByOrganizationConnection( + first: Int + orderBy: EventOrderByInput + skip: Int + where: EventWhereInput + ): [Event!]! getAdvertisements: [Advertisement] getDonationById(id: ID!): Donation! getDonationByOrgId(orgId: ID!): [Donation] - getDonationByOrgIdConnection(first: Int, orgId: ID!, skip: Int, where: DonationWhereInput): [Donation!]! + getDonationByOrgIdConnection( + first: Int + orgId: ID! + skip: Int + where: DonationWhereInput + ): [Donation!]! getPlugins: [Plugin] getlanguage(lang_code: String!): [Translation] hasSubmittedFeedback(eventId: ID!, userId: ID!): Boolean @@ -879,18 +964,47 @@ type Query { me: User! myLanguage: String organizations(id: ID, orderBy: OrganizationOrderByInput): [Organization] - organizationsConnection(first: Int, orderBy: OrganizationOrderByInput, skip: Int, where: OrganizationWhereInput): [Organization]! - organizationsMemberConnection(first: Int, orderBy: UserOrderByInput, orgId: ID!, skip: Int, where: UserWhereInput): UserConnection! + organizationsConnection( + first: Int + orderBy: OrganizationOrderByInput + skip: Int + where: OrganizationWhereInput + ): [Organization]! + organizationsMemberConnection( + first: Int + orderBy: UserOrderByInput + orgId: ID! + skip: Int + where: UserWhereInput + ): UserConnection! plugin(orgId: ID!): [Plugin] post(id: ID!): Post postsByOrganization(id: ID!, orderBy: PostOrderByInput): [Post] - postsByOrganizationConnection(first: Int, id: ID!, orderBy: PostOrderByInput, skip: Int, where: PostWhereInput): PostConnection + postsByOrganizationConnection( + first: Int + id: ID! + orderBy: PostOrderByInput + skip: Int + where: PostWhereInput + ): PostConnection registeredEventsByUser(id: ID, orderBy: EventOrderByInput): [Event] registrantsByEvent(id: ID!): [User] user(id: ID!): User! userLanguage(userId: ID!): String - users(adminApproved: Boolean, first: Int, orderBy: UserOrderByInput, skip: Int, userType: String, where: UserWhereInput): [User] - usersConnection(first: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User]! + users( + adminApproved: Boolean + first: Int + orderBy: UserOrderByInput + skip: Int + userType: String + where: UserWhereInput + ): [User] + usersConnection( + first: Int + orderBy: UserOrderByInput + skip: Int + where: UserWhereInput + ): [User]! } input RecaptchaVerification { @@ -1061,7 +1175,13 @@ type User { phone: UserPhone pluginCreationAllowed: Boolean! registeredEvents: [Event] - tagsAssignedWith(after: String, before: String, first: PositiveInt, last: PositiveInt, organizationId: ID): UserTagsConnection + tagsAssignedWith( + after: String + before: String + first: PositiveInt + last: PositiveInt + organizationId: ID + ): UserTagsConnection tokenVersion: Int! updatedAt: DateTime! userType: UserType! @@ -1244,4 +1364,4 @@ input createGroupChatInput { input createUserFamilyInput { title: String! userIds: [ID!]! -} \ No newline at end of file +} From 2352fd2b89af9e4c4b78740eb2ba63443a7484ff Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 13:01:01 +0530 Subject: [PATCH 10/29] restore package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 52ba0e5746..e027a988bb 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "lint:check": "eslint . --max-warnings=1500", "lint:fix": "eslint . --fix", "lint-staged": "lint-staged", - "format:fix": "prettier --write", + "format:fix": "prettier --write \"**/*.{ts,tsx,json,scss,css}\"", "format:check": "prettier --check \"**/*.{ts,tsx,json,scss,css}\"", "prepare": "husky install", "generate:graphql-markdown": "graphql-markdown http://localhost:4000/graphql > docs/Schema.md", From fe4de230d28957f36ae9358bdb79c07942cdf7f2 Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 14:21:02 +0530 Subject: [PATCH 11/29] fix failing test --- schema.graphql | 182 ++++--------------- tests/resolvers/Mutation/createEvent.spec.ts | 8 +- 2 files changed, 38 insertions(+), 152 deletions(-) diff --git a/schema.graphql b/schema.graphql index 1a924e930a..3def5ece74 100644 --- a/schema.graphql +++ b/schema.graphql @@ -523,17 +523,9 @@ type Mutation { addEventAttendee(data: EventAttendeeInput!): User! addFeedback(data: FeedbackInput!): Feedback! addLanguageTranslation(data: LanguageInput!): Language! - addOrganizationCustomField( - name: String! - organizationId: ID! - type: String! - ): OrganizationCustomField! + addOrganizationCustomField(name: String!, organizationId: ID!, type: String!): OrganizationCustomField! addOrganizationImage(file: String!, organizationId: String!): Organization! - addUserCustomData( - dataName: String! - dataValue: Any! - organizationId: ID! - ): UserCustomData! + addUserCustomData(dataName: String!, dataValue: Any!, organizationId: ID!): UserCustomData! addUserImage(file: String!): User! addUserToGroupChat(chatId: ID!, userId: ID!): GroupChat! addUserToUserFamily(familyId: ID!, userId: ID!): UserFamily! @@ -544,47 +536,19 @@ type Mutation { blockUser(organizationId: ID!, userId: ID!): User! cancelMembershipRequest(membershipRequestId: ID!): MembershipRequest! checkIn(data: CheckInInput!): CheckIn! - createActionItem( - actionItemCategoryId: ID! - data: CreateActionItemInput! - ): ActionItem! - createActionItemCategory( - name: String! - organizationId: ID! - ): ActionItemCategory! + createActionItem(actionItemCategoryId: ID!, data: CreateActionItemInput!): ActionItem! + createActionItemCategory(name: String!, organizationId: ID!): ActionItemCategory! createAdmin(data: UserAndOrganizationInput!): User! - createAdvertisement( - endDate: Date! - link: String! - name: String! - orgId: ID! - startDate: Date! - type: String! - ): Advertisement! + createAdvertisement(endDate: Date!, link: String!, name: String!, orgId: ID!, startDate: Date!, type: String!): Advertisement! createComment(data: CommentInput!, postId: ID!): Comment createDirectChat(data: createChatInput!): DirectChat! - createDonation( - amount: Float! - nameOfOrg: String! - nameOfUser: String! - orgId: ID! - payPalId: ID! - userId: ID! - ): Donation! - createEvent( - data: EventInput! - recurrenceRuleData: RecurrenceRuleInput - ): Event! + createDonation(amount: Float!, nameOfOrg: String!, nameOfUser: String!, orgId: ID!, payPalId: ID!, userId: ID!): Donation! + createEvent(data: EventInput!, recurrenceRuleData: RecurrenceRuleInput): Event! createGroupChat(data: createGroupChatInput!): GroupChat! createMember(input: UserAndOrganizationInput!): Organization! createMessageChat(data: MessageChatInput!): MessageChat! createOrganization(data: OrganizationInput, file: String): Organization! - createPlugin( - pluginCreatedBy: String! - pluginDesc: String! - pluginName: String! - uninstalledOrgs: [ID!] - ): Plugin! + createPlugin(pluginCreatedBy: String!, pluginDesc: String!, pluginName: String!, uninstalledOrgs: [ID!]): Plugin! createPost(data: PostInput!, file: String): Post createSampleOrganization: Boolean! createUserFamily(data: createUserFamilyInput!): UserFamily! @@ -614,10 +578,7 @@ type Mutation { removeGroupChat(chatId: ID!): GroupChat! removeMember(data: UserAndOrganizationInput!): Organization! removeOrganization(id: ID!): User! - removeOrganizationCustomField( - customFieldId: ID! - organizationId: ID! - ): OrganizationCustomField! + removeOrganizationCustomField(customFieldId: ID!, organizationId: ID!): OrganizationCustomField! removeOrganizationImage(organizationId: String!): Organization! removePost(id: ID!): Post removeSampleOrganization: Boolean! @@ -630,14 +591,8 @@ type Mutation { revokeRefreshTokenForUser: Boolean! saveFcmToken(token: String): Boolean! sendMembershipRequest(organizationId: ID!): MembershipRequest! - sendMessageToDirectChat( - chatId: ID! - messageContent: String! - ): DirectChatMessage! - sendMessageToGroupChat( - chatId: ID! - messageContent: String! - ): GroupChatMessage! + sendMessageToDirectChat(chatId: ID!, messageContent: String!): DirectChatMessage! + sendMessageToGroupChat(chatId: ID!, messageContent: String!): GroupChatMessage! signUp(data: UserInput!, file: String): AuthData! togglePostPin(id: ID!, title: String): Post! unassignUserTag(input: ToggleUserTagAssignInput!): User @@ -646,29 +601,16 @@ type Mutation { unlikePost(id: ID!): Post unregisterForEventByUser(id: ID!): Event! updateActionItem(data: UpdateActionItemInput!, id: ID!): ActionItem - updateActionItemCategory( - data: UpdateActionItemCategoryInput! - id: ID! - ): ActionItemCategory - updateAdvertisement( - input: UpdateAdvertisementInput! - ): UpdateAdvertisementPayload + updateActionItemCategory(data: UpdateActionItemCategoryInput!, id: ID!): ActionItemCategory + updateAdvertisement(input: UpdateAdvertisementInput!): UpdateAdvertisementPayload updateEvent(data: UpdateEventInput, id: ID!): Event! updateLanguage(languageCode: String!): User! - updateOrganization( - data: UpdateOrganizationInput - file: String - id: ID! - ): Organization! + updateOrganization(data: UpdateOrganizationInput, file: String, id: ID!): Organization! updatePluginStatus(id: ID!, orgId: ID!): Plugin! updatePost(data: PostUpdateInput, id: ID!): Post! updateUserPassword(data: UpdateUserPasswordInput!): User! updateUserProfile(data: UpdateUserInput, file: String): User! - updateUserRoleInOrganization( - organizationId: ID! - role: String! - userId: ID! - ): Organization! + updateUserRoleInOrganization(organizationId: ID!, role: String!, userId: ID!): Organization! updateUserTag(input: UpdateUserTagInput!): UserTag updateUserType(data: UpdateUserTypeInput!): Boolean! } @@ -695,12 +637,7 @@ type Organization { pinnedPosts: [Post] updatedAt: DateTime! userRegistrationRequired: Boolean! - userTags( - after: String - before: String - first: PositiveInt - last: PositiveInt - ): UserTagsConnection + userTags(after: String, before: String, first: PositiveInt, last: PositiveInt): UserTagsConnection visibleInSearch: Boolean! } @@ -779,20 +716,14 @@ type OtpData { otpToken: String! } -""" -Information about pagination in a connection. -""" +"""Information about pagination in a connection.""" type PageInfo { currPageNo: Int - """ - When paginating forwards, are there more items? - """ + """When paginating forwards, are there more items?""" hasNextPage: Boolean! - """ - When paginating backwards, are there more items? - """ + """When paginating backwards, are there more items?""" hasPreviousPage: Boolean! nextPageNo: Int prevPageNo: Int @@ -853,20 +784,14 @@ type Post { videoUrl: URL } -""" -A connection to a list of items. -""" +"""A connection to a list of items.""" type PostConnection { aggregate: AggregatePost! - """ - A list of edges. - """ + """A list of edges.""" edges: [Post]! - """ - Information to aid in pagination. - """ + """Information to aid in pagination.""" pageInfo: PageInfo! } @@ -941,21 +866,11 @@ type Query { directChatsMessagesByChatID(id: ID!): [DirectChatMessage] event(id: ID!): Event eventsByOrganization(id: ID, orderBy: EventOrderByInput): [Event] - eventsByOrganizationConnection( - first: Int - orderBy: EventOrderByInput - skip: Int - where: EventWhereInput - ): [Event!]! + eventsByOrganizationConnection(first: Int, orderBy: EventOrderByInput, skip: Int, where: EventWhereInput): [Event!]! getAdvertisements: [Advertisement] getDonationById(id: ID!): Donation! getDonationByOrgId(orgId: ID!): [Donation] - getDonationByOrgIdConnection( - first: Int - orgId: ID! - skip: Int - where: DonationWhereInput - ): [Donation!]! + getDonationByOrgIdConnection(first: Int, orgId: ID!, skip: Int, where: DonationWhereInput): [Donation!]! getPlugins: [Plugin] getlanguage(lang_code: String!): [Translation] hasSubmittedFeedback(eventId: ID!, userId: ID!): Boolean @@ -964,47 +879,18 @@ type Query { me: User! myLanguage: String organizations(id: ID, orderBy: OrganizationOrderByInput): [Organization] - organizationsConnection( - first: Int - orderBy: OrganizationOrderByInput - skip: Int - where: OrganizationWhereInput - ): [Organization]! - organizationsMemberConnection( - first: Int - orderBy: UserOrderByInput - orgId: ID! - skip: Int - where: UserWhereInput - ): UserConnection! + organizationsConnection(first: Int, orderBy: OrganizationOrderByInput, skip: Int, where: OrganizationWhereInput): [Organization]! + organizationsMemberConnection(first: Int, orderBy: UserOrderByInput, orgId: ID!, skip: Int, where: UserWhereInput): UserConnection! plugin(orgId: ID!): [Plugin] post(id: ID!): Post postsByOrganization(id: ID!, orderBy: PostOrderByInput): [Post] - postsByOrganizationConnection( - first: Int - id: ID! - orderBy: PostOrderByInput - skip: Int - where: PostWhereInput - ): PostConnection + postsByOrganizationConnection(first: Int, id: ID!, orderBy: PostOrderByInput, skip: Int, where: PostWhereInput): PostConnection registeredEventsByUser(id: ID, orderBy: EventOrderByInput): [Event] registrantsByEvent(id: ID!): [User] user(id: ID!): User! userLanguage(userId: ID!): String - users( - adminApproved: Boolean - first: Int - orderBy: UserOrderByInput - skip: Int - userType: String - where: UserWhereInput - ): [User] - usersConnection( - first: Int - orderBy: UserOrderByInput - skip: Int - where: UserWhereInput - ): [User]! + users(adminApproved: Boolean, first: Int, orderBy: UserOrderByInput, skip: Int, userType: String, where: UserWhereInput): [User] + usersConnection(first: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User]! } input RecaptchaVerification { @@ -1175,13 +1061,7 @@ type User { phone: UserPhone pluginCreationAllowed: Boolean! registeredEvents: [Event] - tagsAssignedWith( - after: String - before: String - first: PositiveInt - last: PositiveInt - organizationId: ID - ): UserTagsConnection + tagsAssignedWith(after: String, before: String, first: PositiveInt, last: PositiveInt, organizationId: ID): UserTagsConnection tokenVersion: Int! updatedAt: DateTime! userType: UserType! @@ -1364,4 +1244,4 @@ input createGroupChatInput { input createUserFamilyInput { title: String! userIds: [ID!]! -} +} \ No newline at end of file diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 446b891d35..7e9b2a7787 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -3,7 +3,11 @@ import type mongoose from "mongoose"; import { Types } from "mongoose"; import { User, Organization, EventAttendee, Event } from "../../../src/models"; import type { MutationCreateEventArgs } from "../../../src/types/generatedGraphQLTypes"; -import { connect, disconnect } from "../../helpers/db"; +import { + connect, + disconnect, + dropAllCollectionsFromDatabase, +} from "../../helpers/db"; import { LENGTH_VALIDATION_ERROR, @@ -31,6 +35,7 @@ let MONGOOSE_INSTANCE: typeof mongoose; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); + await dropAllCollectionsFromDatabase(MONGOOSE_INSTANCE); testUser = await createTestUser(); testOrganization = await Organization.create({ @@ -60,6 +65,7 @@ beforeAll(async () => { }); afterAll(async () => { + await dropAllCollectionsFromDatabase(MONGOOSE_INSTANCE); await disconnect(MONGOOSE_INSTANCE); }); From 3d4c5287de77755927a51e35a6c06e67fd2d25a5 Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 15:43:30 +0530 Subject: [PATCH 12/29] better implementation and faster code --- .../associateEventWithUser.ts | 42 ----------------- .../createRecurringEvents.ts | 45 ++++++++++++------- .../createEventHelpers/createSingleEvent.ts | 35 +++++++++++---- 3 files changed, 57 insertions(+), 65 deletions(-) delete mode 100644 src/helpers/event/createEventHelpers/associateEventWithUser.ts diff --git a/src/helpers/event/createEventHelpers/associateEventWithUser.ts b/src/helpers/event/createEventHelpers/associateEventWithUser.ts deleted file mode 100644 index a165549155..0000000000 --- a/src/helpers/event/createEventHelpers/associateEventWithUser.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type mongoose from "mongoose"; -import { EventAttendee, User } from "../../../models"; - -/** - * This function associates an event with the user. - * @param currentUserId - _id of the current user. - * @param createdEventId - _id of the event. - * @remarks The following steps are followed: - * 1. Create an EventAttendee (adding the user as an attendee). - * 2. Update the event related user fields. - */ - -export const associateEventWithUser = async ( - currentUserId: string, - createdEventId: string, - session: mongoose.ClientSession -): Promise => { - await Promise.all([ - EventAttendee.create( - [ - { - userId: currentUserId, - eventId: createdEventId, - }, - ], - { session } - ), - User.updateOne( - { - _id: currentUserId, - }, - { - $push: { - eventAdmin: createdEventId, - createdEvents: createdEventId, - registeredEvents: createdEventId, - }, - }, - { session } - ), - ]); -}; diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts index 09eeff80a7..1f91150aa6 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -1,6 +1,6 @@ import type mongoose from "mongoose"; import type { InterfaceEvent } from "../../../models"; -import { Event } from "../../../models"; +import { Event, EventAttendee, User } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; import { createRecurrenceRule, @@ -8,7 +8,6 @@ import { getRecurringInstanceDates, } from "../recurringEventHelpers"; import { generateRecurrenceRuleString } from "../recurringEventHelpers/generateRecurrenceRuleString"; -import { associateEventWithUser } from "./associateEventWithUser"; import { cacheEvents } from "../../../services/EventCache/cacheEvents"; import { format } from "date-fns"; @@ -24,7 +23,8 @@ import { format } from "date-fns"; * 4. Getting the dates for recurring instances. * 5. Creating a recurrenceRule document. * 6. Generating recurring instances according to the recurrence rule. - * 7. Associating the instances with the user and caching them. + * 7. Associating the instances with the user + * 8. Cache the instances. * @returns Created recurring event instances */ @@ -106,20 +106,35 @@ export const createRecurringEvents = async ( }); // associate the instances with the user and cache them - const associateAndCacheInstances = recurringEventInstances.map( - async (recurringEventInstance) => { - await Promise.all([ - associateEventWithUser( - currentUserId, - recurringEventInstance?._id.toString(), - session - ), - cacheEvents([recurringEventInstance]), - ]); - } + const eventAttendees = recurringEventInstances.map( + (recurringEventInstance) => ({ + userId: currentUserId, + eventId: recurringEventInstance?._id.toString(), + }) ); - await Promise.all(associateAndCacheInstances); + await EventAttendee.insertMany(eventAttendees, { session }); + + const eventIds = eventAttendees.map((eventAttendee) => eventAttendee.eventId); + + await User.updateOne( + { _id: currentUserId }, + { + $push: { + eventAdmin: { $each: eventIds }, + createdEvents: { $each: eventIds }, + registeredEvents: { $each: eventIds }, + }, + }, + { session } + ); + + // cache the instances + await Promise.all( + recurringEventInstances.map((recurringEventInstance) => + cacheEvents([recurringEventInstance]) + ) + ); return recurringEventInstances; }; diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index 296afc80df..f0354b46e5 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -1,8 +1,7 @@ import type mongoose from "mongoose"; import type { InterfaceEvent } from "../../../models"; -import { Event } from "../../../models"; +import { Event, EventAttendee, User } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; -import { associateEventWithUser } from "./associateEventWithUser"; import { cacheEvents } from "../../../services/EventCache/cacheEvents"; import { format } from "date-fns"; @@ -13,7 +12,8 @@ import { format } from "date-fns"; * @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 and cache it. + * 2. Associate the event with the user + * 3. Cache the event. * @returns The event generated. */ @@ -41,12 +41,31 @@ export const createSingleEvent = async ( { session } ); - // associate the event with the user - await associateEventWithUser( - currentUserId, - createdEvent[0]?._id.toString(), - session + // associate event with the user + await EventAttendee.create( + [ + { + userId: currentUserId, + eventId: createdEvent[0]?._id, + }, + ], + { session } ); + await User.updateOne( + { + _id: currentUserId, + }, + { + $push: { + eventAdmin: createdEvent[0]?._id, + createdEvents: createdEvent[0]?._id, + registeredEvents: createdEvent[0]?._id, + }, + }, + { session } + ); + + // cache the event await cacheEvents([createdEvent[0]]); return createdEvent[0]; From e2f47be1960211800497aaca1849768c9baabe2b Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 15:56:54 +0530 Subject: [PATCH 13/29] minor refactoring --- .../createRecurringEvents.ts | 18 +++++++++--------- .../event/recurringEventHelpers/index.ts | 6 ++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts index 1f91150aa6..3e52d84a33 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -3,11 +3,11 @@ import type { InterfaceEvent } from "../../../models"; import { Event, EventAttendee, User } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; import { + generateRecurrenceRuleString, + getRecurringInstanceDates, createRecurrenceRule, generateRecurringEventInstances, - getRecurringInstanceDates, } from "../recurringEventHelpers"; -import { generateRecurrenceRuleString } from "../recurringEventHelpers/generateRecurrenceRuleString"; import { cacheEvents } from "../../../services/EventCache/cacheEvents"; import { format } from "date-fns"; @@ -17,13 +17,13 @@ import { format } from "date-fns"; * @param currentUserId - _id of the current user * @param organizationId - _id of the organization the events belongs to * @remarks The following steps are followed: - * 1. Creating a default recurrenceRuleData. - * 2. Generating a recurrence rule string based on the recurrenceRuleData. - * 3. Creating a baseRecurringEvent on which recurring instances would be based. - * 4. Getting the dates for recurring instances. - * 5. Creating a recurrenceRule document. - * 6. Generating recurring instances according to the recurrence rule. - * 7. Associating the instances with the user + * 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. + * 7. Associate the instances with the user * 8. Cache the instances. * @returns Created recurring event instances */ diff --git a/src/helpers/event/recurringEventHelpers/index.ts b/src/helpers/event/recurringEventHelpers/index.ts index ffff1fdec6..f2a9dee263 100644 --- a/src/helpers/event/recurringEventHelpers/index.ts +++ b/src/helpers/event/recurringEventHelpers/index.ts @@ -1,9 +1,11 @@ +import { generateRecurrenceRuleString } from "./generateRecurrenceRuleString"; +import { getRecurringInstanceDates } from "./getRecurringInstanceDates"; import { createRecurrenceRule } from "./createRecurrenceRule"; import { generateRecurringEventInstances } from "./generateRecurringEventInstances"; -import { getRecurringInstanceDates } from "./getRecurringInstanceDates"; export { + generateRecurrenceRuleString, + getRecurringInstanceDates, createRecurrenceRule, generateRecurringEventInstances, - getRecurringInstanceDates, }; From 67d67777082712e41ea9b55692735bd42ac109f2 Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 16:12:21 +0530 Subject: [PATCH 14/29] minor correction --- .../createEventHelpers/createRecurringEvents.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts index 3e52d84a33..fe952aa734 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -44,6 +44,8 @@ export const createRecurringEvents = async ( }; } + // generate a recurrence rule string which would be used to generate rrule object + // and get recurrence dates const recurrenceRuleString = generateRecurrenceRuleString( recurrenceRuleData, data?.startDate, @@ -105,7 +107,7 @@ export const createRecurringEvents = async ( session, }); - // associate the instances with the user and cache them + // associate the instances with the user const eventAttendees = recurringEventInstances.map( (recurringEventInstance) => ({ userId: currentUserId, @@ -115,15 +117,17 @@ export const createRecurringEvents = async ( await EventAttendee.insertMany(eventAttendees, { session }); - const eventIds = eventAttendees.map((eventAttendee) => eventAttendee.eventId); + const eventInstanceIds = recurringEventInstances.map((instance) => + instance._id.toString() + ); await User.updateOne( { _id: currentUserId }, { $push: { - eventAdmin: { $each: eventIds }, - createdEvents: { $each: eventIds }, - registeredEvents: { $each: eventIds }, + eventAdmin: { $each: eventInstanceIds }, + createdEvents: { $each: eventInstanceIds }, + registeredEvents: { $each: eventInstanceIds }, }, }, { session } From 61548295947dfdfb500f59a76d892e45e7d28d69 Mon Sep 17 00:00:00 2001 From: meetul Date: Thu, 8 Feb 2024 17:33:59 +0530 Subject: [PATCH 15/29] fix imports --- src/helpers/event/createEventHelpers/index.ts | 6 ++---- src/helpers/event/recurringEventHelpers/index.ts | 15 ++++----------- src/resolvers/Mutation/createEvent.ts | 2 -- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/helpers/event/createEventHelpers/index.ts b/src/helpers/event/createEventHelpers/index.ts index 0a3e77d301..2c0299750b 100644 --- a/src/helpers/event/createEventHelpers/index.ts +++ b/src/helpers/event/createEventHelpers/index.ts @@ -1,4 +1,2 @@ -import { createSingleEvent } from "./createSingleEvent"; -import { createRecurringEvents } from "./createRecurringEvents"; - -export { createSingleEvent, createRecurringEvents }; +export { createSingleEvent } from "./createSingleEvent"; +export { createRecurringEvents } from "./createRecurringEvents"; diff --git a/src/helpers/event/recurringEventHelpers/index.ts b/src/helpers/event/recurringEventHelpers/index.ts index f2a9dee263..43e66640d5 100644 --- a/src/helpers/event/recurringEventHelpers/index.ts +++ b/src/helpers/event/recurringEventHelpers/index.ts @@ -1,11 +1,4 @@ -import { generateRecurrenceRuleString } from "./generateRecurrenceRuleString"; -import { getRecurringInstanceDates } from "./getRecurringInstanceDates"; -import { createRecurrenceRule } from "./createRecurrenceRule"; -import { generateRecurringEventInstances } from "./generateRecurringEventInstances"; - -export { - generateRecurrenceRuleString, - getRecurringInstanceDates, - createRecurrenceRule, - generateRecurringEventInstances, -}; +export { generateRecurrenceRuleString } from "./generateRecurrenceRuleString"; +export { getRecurringInstanceDates } from "./getRecurringInstanceDates"; +export { createRecurrenceRule } from "./createRecurrenceRule"; +export { generateRecurringEventInstances } from "./generateRecurringEventInstances"; diff --git a/src/resolvers/Mutation/createEvent.ts b/src/resolvers/Mutation/createEvent.ts index 9fd4cdc1bf..921e766c86 100644 --- a/src/resolvers/Mutation/createEvent.ts +++ b/src/resolvers/Mutation/createEvent.ts @@ -154,7 +154,6 @@ export const createEvent: MutationResolvers["createEvent"] = async ( } if (session) { - // commit transaction if everything's successful await session.commitTransaction(); } @@ -162,7 +161,6 @@ export const createEvent: MutationResolvers["createEvent"] = async ( return createdEvent; } catch (error) { if (session) { - // abort transaction if something fails await session.abortTransaction(); } throw error; From 60353fd63b9f5111683dfbc7694f446864c67a22 Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 9 Feb 2024 10:07:28 +0530 Subject: [PATCH 16/29] return single recurring instance --- ...rringEvents.ts => createRecurringEvent.ts} | 50 +++---------------- .../createEventHelpers/createSingleEvent.ts | 8 +-- src/helpers/event/createEventHelpers/index.ts | 2 +- .../generateRecurringEventInstances.ts | 45 +++++++++++++++-- src/resolvers/Mutation/createEvent.ts | 6 +-- 5 files changed, 55 insertions(+), 56 deletions(-) rename src/helpers/event/createEventHelpers/{createRecurringEvents.ts => createRecurringEvent.ts} (71%) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvent.ts similarity index 71% rename from src/helpers/event/createEventHelpers/createRecurringEvents.ts rename to src/helpers/event/createEventHelpers/createRecurringEvent.ts index fe952aa734..90603f35f5 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvent.ts @@ -1,6 +1,6 @@ import type mongoose from "mongoose"; import type { InterfaceEvent } from "../../../models"; -import { Event, EventAttendee, User } from "../../../models"; +import { Event } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; import { generateRecurrenceRuleString, @@ -8,7 +8,6 @@ import { createRecurrenceRule, generateRecurringEventInstances, } from "../recurringEventHelpers"; -import { cacheEvents } from "../../../services/EventCache/cacheEvents"; import { format } from "date-fns"; /** @@ -23,17 +22,15 @@ import { format } from "date-fns"; * 4. Get the dates for recurring instances. * 5. Create a recurrenceRule document. * 6. Generate recurring instances according to the recurrence rule. - * 7. Associate the instances with the user - * 8. Cache the instances. - * @returns Created recurring event instances + * @returns Created recurring event instance */ -export const createRecurringEvents = async ( +export const createRecurringEvent = async ( args: MutationCreateEventArgs, currentUserId: string, organizationId: string, session: mongoose.ClientSession -): Promise => { +): Promise => { const { data } = args; let { recurrenceRuleData } = args; @@ -96,8 +93,8 @@ export const createRecurringEvents = async ( session ); - // generate the recurring instances - const recurringEventInstances = await generateRecurringEventInstances({ + // generate the recurring instances and get an instance back + const recurringEventInstance = await generateRecurringEventInstances({ data, baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), recurrenceRuleId: recurrenceRule?._id.toString(), @@ -107,38 +104,5 @@ export const createRecurringEvents = async ( session, }); - // associate the instances with the user - const eventAttendees = recurringEventInstances.map( - (recurringEventInstance) => ({ - userId: currentUserId, - eventId: recurringEventInstance?._id.toString(), - }) - ); - - await EventAttendee.insertMany(eventAttendees, { session }); - - const eventInstanceIds = recurringEventInstances.map((instance) => - instance._id.toString() - ); - - await User.updateOne( - { _id: currentUserId }, - { - $push: { - eventAdmin: { $each: eventInstanceIds }, - createdEvents: { $each: eventInstanceIds }, - registeredEvents: { $each: eventInstanceIds }, - }, - }, - { session } - ); - - // cache the instances - await Promise.all( - recurringEventInstances.map((recurringEventInstance) => - cacheEvents([recurringEventInstance]) - ) - ); - - return recurringEventInstances; + return recurringEventInstance; }; diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index f0354b46e5..1b34489ee3 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -18,13 +18,13 @@ import { format } from "date-fns"; */ export const createSingleEvent = async ( - args: Partial, + args: MutationCreateEventArgs, currentUserId: string, organizationId: string, session: mongoose.ClientSession -): Promise> => { - const formattedStartDate = format(args.data?.startDate, "yyyy-MM-dd"); - const formattedEndDate = format(args.data?.endDate, "yyyy-MM-dd"); +): Promise => { + const formattedStartDate = format(args.data.startDate, "yyyy-MM-dd"); + const formattedEndDate = format(args.data.endDate, "yyyy-MM-dd"); // create the single event const createdEvent = await Event.create( diff --git a/src/helpers/event/createEventHelpers/index.ts b/src/helpers/event/createEventHelpers/index.ts index 2c0299750b..ebab56acb9 100644 --- a/src/helpers/event/createEventHelpers/index.ts +++ b/src/helpers/event/createEventHelpers/index.ts @@ -1,2 +1,2 @@ export { createSingleEvent } from "./createSingleEvent"; -export { createRecurringEvents } from "./createRecurringEvents"; +export { createRecurringEvent } from "./createRecurringEvent"; diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index a4099741ed..c02ff57e19 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -1,8 +1,9 @@ import type mongoose from "mongoose"; import { format } from "date-fns"; import type { InterfaceEvent } from "../../../models"; -import { Event } from "../../../models"; +import { Event, EventAttendee, User } from "../../../models"; import type { EventInput } from "../../../types/generatedGraphQLTypes"; +import { cacheEvents } from "../../../services/EventCache/cacheEvents"; /** * This function generates the recurring event instances. @@ -15,7 +16,9 @@ import type { EventInput } from "../../../types/generatedGraphQLTypes"; * @remarks The following steps are followed: * 1. Generate the instances for each provided date. * 2. Insert the documents in the database. - * @returns The recurring instances generated during this operation. + * 3. Associate the instances with the user. + * 4. Cache the instances. + * @returns A recurring instance generated during this operation. */ interface InterfaceGenerateRecurringInstances { @@ -45,7 +48,7 @@ export const generateRecurringEventInstances = async ({ currentUserId, organizationId, session, -}: InterfaceGenerateRecurringInstances): Promise => { +}: InterfaceGenerateRecurringInstances): Promise => { const recurringInstances: InterfaceRecurringEvent[] = []; recurringInstanceDates.map((date) => { const formattedInstanceDate = format(date, "yyyy-MM-dd"); @@ -71,5 +74,39 @@ export const generateRecurringEventInstances = async ({ session, }); - return recurringEventInstances; + // add eventattendee for each instance + const eventAttendees = recurringEventInstances.map( + (recurringEventInstance) => ({ + userId: currentUserId, + eventId: recurringEventInstance?._id.toString(), + }) + ); + + await EventAttendee.insertMany(eventAttendees, { session }); + + const eventInstanceIds = recurringEventInstances.map((instance) => + instance._id.toString() + ); + + // update user event fields to include generated instances + await User.updateOne( + { _id: currentUserId }, + { + $push: { + eventAdmin: { $each: eventInstanceIds }, + createdEvents: { $each: eventInstanceIds }, + registeredEvents: { $each: eventInstanceIds }, + }, + }, + { session } + ); + + // cache the instances + await Promise.all( + recurringEventInstances.map((recurringEventInstance) => + cacheEvents([recurringEventInstance]) + ) + ); + + return recurringEventInstances[0]; }; diff --git a/src/resolvers/Mutation/createEvent.ts b/src/resolvers/Mutation/createEvent.ts index 921e766c86..fdf7b700e6 100644 --- a/src/resolvers/Mutation/createEvent.ts +++ b/src/resolvers/Mutation/createEvent.ts @@ -13,7 +13,7 @@ import { compareDates } from "../../libraries/validators/compareDates"; import { session } from "../../db"; import { createSingleEvent, - createRecurringEvents, + createRecurringEvent, } from "../../helpers/event/createEventHelpers"; /** @@ -135,14 +135,12 @@ export const createEvent: MutationResolvers["createEvent"] = async ( if (args.data.recurring) { // create recurring event instances - const recurringEventInstances = await createRecurringEvents( + createdEvent = await createRecurringEvent( args, currentUser?._id.toString(), organization?._id.toString(), session ); - - createdEvent = recurringEventInstances[0]; } else { // create a single non-recurring event createdEvent = await createSingleEvent( From 09100f0c016b15a507c59e1903c520f97fe9458f Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 9 Feb 2024 10:20:39 +0530 Subject: [PATCH 17/29] fix failing test --- tests/resolvers/Mutation/createEvent.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 7e9b2a7787..3615413960 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -402,7 +402,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); let startDate = new Date(); - startDate = addMonths(startDate, 1); + startDate = addMonths(startDate, 3); const args: MutationCreateEventArgs = { data: { From fc12a9947d3d4d2de1a2142316951c1d8368c649 Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 9 Feb 2024 11:26:59 +0530 Subject: [PATCH 18/29] Revert "return single recurring instance" This reverts commit 60353fd63b9f5111683dfbc7694f446864c67a22. --- ...rringEvent.ts => createRecurringEvents.ts} | 50 ++++++++++++++++--- .../createEventHelpers/createSingleEvent.ts | 8 +-- src/helpers/event/createEventHelpers/index.ts | 2 +- .../generateRecurringEventInstances.ts | 45 ++--------------- src/resolvers/Mutation/createEvent.ts | 6 ++- 5 files changed, 56 insertions(+), 55 deletions(-) rename src/helpers/event/createEventHelpers/{createRecurringEvent.ts => createRecurringEvents.ts} (71%) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvent.ts b/src/helpers/event/createEventHelpers/createRecurringEvents.ts similarity index 71% rename from src/helpers/event/createEventHelpers/createRecurringEvent.ts rename to src/helpers/event/createEventHelpers/createRecurringEvents.ts index 90603f35f5..fe952aa734 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvent.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvents.ts @@ -1,6 +1,6 @@ import type mongoose from "mongoose"; import type { InterfaceEvent } from "../../../models"; -import { Event } from "../../../models"; +import { Event, EventAttendee, User } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; import { generateRecurrenceRuleString, @@ -8,6 +8,7 @@ import { createRecurrenceRule, generateRecurringEventInstances, } from "../recurringEventHelpers"; +import { cacheEvents } from "../../../services/EventCache/cacheEvents"; import { format } from "date-fns"; /** @@ -22,15 +23,17 @@ import { format } from "date-fns"; * 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 + * 7. Associate the instances with the user + * 8. Cache the instances. + * @returns Created recurring event instances */ -export const createRecurringEvent = async ( +export const createRecurringEvents = async ( args: MutationCreateEventArgs, currentUserId: string, organizationId: string, session: mongoose.ClientSession -): Promise => { +): Promise => { const { data } = args; let { recurrenceRuleData } = args; @@ -93,8 +96,8 @@ export const createRecurringEvent = async ( session ); - // generate the recurring instances and get an instance back - const recurringEventInstance = await generateRecurringEventInstances({ + // generate the recurring instances + const recurringEventInstances = await generateRecurringEventInstances({ data, baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), recurrenceRuleId: recurrenceRule?._id.toString(), @@ -104,5 +107,38 @@ export const createRecurringEvent = async ( session, }); - return recurringEventInstance; + // associate the instances with the user + const eventAttendees = recurringEventInstances.map( + (recurringEventInstance) => ({ + userId: currentUserId, + eventId: recurringEventInstance?._id.toString(), + }) + ); + + await EventAttendee.insertMany(eventAttendees, { session }); + + const eventInstanceIds = recurringEventInstances.map((instance) => + instance._id.toString() + ); + + await User.updateOne( + { _id: currentUserId }, + { + $push: { + eventAdmin: { $each: eventInstanceIds }, + createdEvents: { $each: eventInstanceIds }, + registeredEvents: { $each: eventInstanceIds }, + }, + }, + { session } + ); + + // cache the instances + await Promise.all( + recurringEventInstances.map((recurringEventInstance) => + cacheEvents([recurringEventInstance]) + ) + ); + + return recurringEventInstances; }; diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index 1b34489ee3..f0354b46e5 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -18,13 +18,13 @@ import { format } from "date-fns"; */ export const createSingleEvent = async ( - args: MutationCreateEventArgs, + args: Partial, currentUserId: string, organizationId: string, session: mongoose.ClientSession -): Promise => { - const formattedStartDate = format(args.data.startDate, "yyyy-MM-dd"); - const formattedEndDate = format(args.data.endDate, "yyyy-MM-dd"); +): Promise> => { + const formattedStartDate = format(args.data?.startDate, "yyyy-MM-dd"); + const formattedEndDate = format(args.data?.endDate, "yyyy-MM-dd"); // create the single event const createdEvent = await Event.create( diff --git a/src/helpers/event/createEventHelpers/index.ts b/src/helpers/event/createEventHelpers/index.ts index ebab56acb9..2c0299750b 100644 --- a/src/helpers/event/createEventHelpers/index.ts +++ b/src/helpers/event/createEventHelpers/index.ts @@ -1,2 +1,2 @@ export { createSingleEvent } from "./createSingleEvent"; -export { createRecurringEvent } from "./createRecurringEvent"; +export { createRecurringEvents } from "./createRecurringEvents"; diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index c02ff57e19..a4099741ed 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -1,9 +1,8 @@ import type mongoose from "mongoose"; import { format } from "date-fns"; import type { InterfaceEvent } from "../../../models"; -import { Event, EventAttendee, User } from "../../../models"; +import { Event } from "../../../models"; import type { EventInput } from "../../../types/generatedGraphQLTypes"; -import { cacheEvents } from "../../../services/EventCache/cacheEvents"; /** * This function generates the recurring event instances. @@ -16,9 +15,7 @@ import { cacheEvents } from "../../../services/EventCache/cacheEvents"; * @remarks The following steps are followed: * 1. Generate the instances for each provided date. * 2. Insert the documents in the database. - * 3. Associate the instances with the user. - * 4. Cache the instances. - * @returns A recurring instance generated during this operation. + * @returns The recurring instances generated during this operation. */ interface InterfaceGenerateRecurringInstances { @@ -48,7 +45,7 @@ export const generateRecurringEventInstances = async ({ currentUserId, organizationId, session, -}: InterfaceGenerateRecurringInstances): Promise => { +}: InterfaceGenerateRecurringInstances): Promise => { const recurringInstances: InterfaceRecurringEvent[] = []; recurringInstanceDates.map((date) => { const formattedInstanceDate = format(date, "yyyy-MM-dd"); @@ -74,39 +71,5 @@ export const generateRecurringEventInstances = async ({ session, }); - // add eventattendee for each instance - const eventAttendees = recurringEventInstances.map( - (recurringEventInstance) => ({ - userId: currentUserId, - eventId: recurringEventInstance?._id.toString(), - }) - ); - - await EventAttendee.insertMany(eventAttendees, { session }); - - const eventInstanceIds = recurringEventInstances.map((instance) => - instance._id.toString() - ); - - // update user event fields to include generated instances - await User.updateOne( - { _id: currentUserId }, - { - $push: { - eventAdmin: { $each: eventInstanceIds }, - createdEvents: { $each: eventInstanceIds }, - registeredEvents: { $each: eventInstanceIds }, - }, - }, - { session } - ); - - // cache the instances - await Promise.all( - recurringEventInstances.map((recurringEventInstance) => - cacheEvents([recurringEventInstance]) - ) - ); - - return recurringEventInstances[0]; + return recurringEventInstances; }; diff --git a/src/resolvers/Mutation/createEvent.ts b/src/resolvers/Mutation/createEvent.ts index fdf7b700e6..921e766c86 100644 --- a/src/resolvers/Mutation/createEvent.ts +++ b/src/resolvers/Mutation/createEvent.ts @@ -13,7 +13,7 @@ import { compareDates } from "../../libraries/validators/compareDates"; import { session } from "../../db"; import { createSingleEvent, - createRecurringEvent, + createRecurringEvents, } from "../../helpers/event/createEventHelpers"; /** @@ -135,12 +135,14 @@ export const createEvent: MutationResolvers["createEvent"] = async ( if (args.data.recurring) { // create recurring event instances - createdEvent = await createRecurringEvent( + const recurringEventInstances = await createRecurringEvents( args, currentUser?._id.toString(), organization?._id.toString(), session ); + + createdEvent = recurringEventInstances[0]; } else { // create a single non-recurring event createdEvent = await createSingleEvent( From f06a72cd0116edf3a4fdee61756d1bb933515f78 Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 9 Feb 2024 11:39:54 +0530 Subject: [PATCH 19/29] Reapply "return single recurring instance" This reverts commit fc12a9947d3d4d2de1a2142316951c1d8368c649. --- ...rringEvents.ts => createRecurringEvent.ts} | 50 +++---------------- .../createEventHelpers/createSingleEvent.ts | 8 +-- src/helpers/event/createEventHelpers/index.ts | 2 +- .../generateRecurringEventInstances.ts | 45 +++++++++++++++-- src/resolvers/Mutation/createEvent.ts | 6 +-- 5 files changed, 55 insertions(+), 56 deletions(-) rename src/helpers/event/createEventHelpers/{createRecurringEvents.ts => createRecurringEvent.ts} (71%) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvents.ts b/src/helpers/event/createEventHelpers/createRecurringEvent.ts similarity index 71% rename from src/helpers/event/createEventHelpers/createRecurringEvents.ts rename to src/helpers/event/createEventHelpers/createRecurringEvent.ts index fe952aa734..90603f35f5 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvents.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvent.ts @@ -1,6 +1,6 @@ import type mongoose from "mongoose"; import type { InterfaceEvent } from "../../../models"; -import { Event, EventAttendee, User } from "../../../models"; +import { Event } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; import { generateRecurrenceRuleString, @@ -8,7 +8,6 @@ import { createRecurrenceRule, generateRecurringEventInstances, } from "../recurringEventHelpers"; -import { cacheEvents } from "../../../services/EventCache/cacheEvents"; import { format } from "date-fns"; /** @@ -23,17 +22,15 @@ import { format } from "date-fns"; * 4. Get the dates for recurring instances. * 5. Create a recurrenceRule document. * 6. Generate recurring instances according to the recurrence rule. - * 7. Associate the instances with the user - * 8. Cache the instances. - * @returns Created recurring event instances + * @returns Created recurring event instance */ -export const createRecurringEvents = async ( +export const createRecurringEvent = async ( args: MutationCreateEventArgs, currentUserId: string, organizationId: string, session: mongoose.ClientSession -): Promise => { +): Promise => { const { data } = args; let { recurrenceRuleData } = args; @@ -96,8 +93,8 @@ export const createRecurringEvents = async ( session ); - // generate the recurring instances - const recurringEventInstances = await generateRecurringEventInstances({ + // generate the recurring instances and get an instance back + const recurringEventInstance = await generateRecurringEventInstances({ data, baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), recurrenceRuleId: recurrenceRule?._id.toString(), @@ -107,38 +104,5 @@ export const createRecurringEvents = async ( session, }); - // associate the instances with the user - const eventAttendees = recurringEventInstances.map( - (recurringEventInstance) => ({ - userId: currentUserId, - eventId: recurringEventInstance?._id.toString(), - }) - ); - - await EventAttendee.insertMany(eventAttendees, { session }); - - const eventInstanceIds = recurringEventInstances.map((instance) => - instance._id.toString() - ); - - await User.updateOne( - { _id: currentUserId }, - { - $push: { - eventAdmin: { $each: eventInstanceIds }, - createdEvents: { $each: eventInstanceIds }, - registeredEvents: { $each: eventInstanceIds }, - }, - }, - { session } - ); - - // cache the instances - await Promise.all( - recurringEventInstances.map((recurringEventInstance) => - cacheEvents([recurringEventInstance]) - ) - ); - - return recurringEventInstances; + return recurringEventInstance; }; diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index f0354b46e5..1b34489ee3 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -18,13 +18,13 @@ import { format } from "date-fns"; */ export const createSingleEvent = async ( - args: Partial, + args: MutationCreateEventArgs, currentUserId: string, organizationId: string, session: mongoose.ClientSession -): Promise> => { - const formattedStartDate = format(args.data?.startDate, "yyyy-MM-dd"); - const formattedEndDate = format(args.data?.endDate, "yyyy-MM-dd"); +): Promise => { + const formattedStartDate = format(args.data.startDate, "yyyy-MM-dd"); + const formattedEndDate = format(args.data.endDate, "yyyy-MM-dd"); // create the single event const createdEvent = await Event.create( diff --git a/src/helpers/event/createEventHelpers/index.ts b/src/helpers/event/createEventHelpers/index.ts index 2c0299750b..ebab56acb9 100644 --- a/src/helpers/event/createEventHelpers/index.ts +++ b/src/helpers/event/createEventHelpers/index.ts @@ -1,2 +1,2 @@ export { createSingleEvent } from "./createSingleEvent"; -export { createRecurringEvents } from "./createRecurringEvents"; +export { createRecurringEvent } from "./createRecurringEvent"; diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index a4099741ed..c02ff57e19 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -1,8 +1,9 @@ import type mongoose from "mongoose"; import { format } from "date-fns"; import type { InterfaceEvent } from "../../../models"; -import { Event } from "../../../models"; +import { Event, EventAttendee, User } from "../../../models"; import type { EventInput } from "../../../types/generatedGraphQLTypes"; +import { cacheEvents } from "../../../services/EventCache/cacheEvents"; /** * This function generates the recurring event instances. @@ -15,7 +16,9 @@ import type { EventInput } from "../../../types/generatedGraphQLTypes"; * @remarks The following steps are followed: * 1. Generate the instances for each provided date. * 2. Insert the documents in the database. - * @returns The recurring instances generated during this operation. + * 3. Associate the instances with the user. + * 4. Cache the instances. + * @returns A recurring instance generated during this operation. */ interface InterfaceGenerateRecurringInstances { @@ -45,7 +48,7 @@ export const generateRecurringEventInstances = async ({ currentUserId, organizationId, session, -}: InterfaceGenerateRecurringInstances): Promise => { +}: InterfaceGenerateRecurringInstances): Promise => { const recurringInstances: InterfaceRecurringEvent[] = []; recurringInstanceDates.map((date) => { const formattedInstanceDate = format(date, "yyyy-MM-dd"); @@ -71,5 +74,39 @@ export const generateRecurringEventInstances = async ({ session, }); - return recurringEventInstances; + // add eventattendee for each instance + const eventAttendees = recurringEventInstances.map( + (recurringEventInstance) => ({ + userId: currentUserId, + eventId: recurringEventInstance?._id.toString(), + }) + ); + + await EventAttendee.insertMany(eventAttendees, { session }); + + const eventInstanceIds = recurringEventInstances.map((instance) => + instance._id.toString() + ); + + // update user event fields to include generated instances + await User.updateOne( + { _id: currentUserId }, + { + $push: { + eventAdmin: { $each: eventInstanceIds }, + createdEvents: { $each: eventInstanceIds }, + registeredEvents: { $each: eventInstanceIds }, + }, + }, + { session } + ); + + // cache the instances + await Promise.all( + recurringEventInstances.map((recurringEventInstance) => + cacheEvents([recurringEventInstance]) + ) + ); + + return recurringEventInstances[0]; }; diff --git a/src/resolvers/Mutation/createEvent.ts b/src/resolvers/Mutation/createEvent.ts index 921e766c86..fdf7b700e6 100644 --- a/src/resolvers/Mutation/createEvent.ts +++ b/src/resolvers/Mutation/createEvent.ts @@ -13,7 +13,7 @@ import { compareDates } from "../../libraries/validators/compareDates"; import { session } from "../../db"; import { createSingleEvent, - createRecurringEvents, + createRecurringEvent, } from "../../helpers/event/createEventHelpers"; /** @@ -135,14 +135,12 @@ export const createEvent: MutationResolvers["createEvent"] = async ( if (args.data.recurring) { // create recurring event instances - const recurringEventInstances = await createRecurringEvents( + createdEvent = await createRecurringEvent( args, currentUser?._id.toString(), organization?._id.toString(), session ); - - createdEvent = recurringEventInstances[0]; } else { // create a single non-recurring event createdEvent = await createSingleEvent( From abf5d91dd041c0c79c0913bcfb9bef8741444f6c Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 9 Feb 2024 11:48:49 +0530 Subject: [PATCH 20/29] test commit --- .../generateRecurringEventInstances.ts | 4 ++-- tests/resolvers/Mutation/createEvent.spec.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index c02ff57e19..f4308eee58 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -59,8 +59,8 @@ export const generateRecurringEventInstances = async ({ endDate: formattedInstanceDate, recurring: true, isBaseRecurringEvent: false, - recurrenceRuleId: recurrenceRuleId, - baseRecurringEventId: baseRecurringEventId, + recurrenceRuleId, + baseRecurringEventId, creatorId: currentUserId, admins: [currentUserId], organization: organizationId, diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 3615413960..5d2280ed4b 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -402,7 +402,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); let startDate = new Date(); - startDate = addMonths(startDate, 3); + startDate = addMonths(startDate, 1); const args: MutationCreateEventArgs = { data: { @@ -423,7 +423,7 @@ describe("resolvers -> Mutation -> createEvent", () => { recurrenceRuleData: { frequency: "WEEKLY", weekdays: ["TH", "SA"], - count: 10, + count: 5, }, }; @@ -471,7 +471,7 @@ describe("resolvers -> Mutation -> createEvent", () => { }).lean(); expect(recurringEvents).toBeDefined(); - expect(recurringEvents).toHaveLength(10); + expect(recurringEvents).toHaveLength(5); const attendeeExists = await EventAttendee.exists({ userId: testUser?._id, From ee01799ee12105c04dd97ac943a3fb77cf9609fd Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 9 Feb 2024 12:09:27 +0530 Subject: [PATCH 21/29] update test --- schema.graphql | 2 +- src/constants.ts | 10 ++++++++++ .../generateRecurrenceRuleString.ts | 4 ++-- src/typeDefs/inputs.ts | 2 +- src/types/generatedGraphQLTypes.ts | 2 +- tests/resolvers/Mutation/createEvent.spec.ts | 11 ++++++++--- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/schema.graphql b/schema.graphql index 3def5ece74..3a9565b223 100644 --- a/schema.graphql +++ b/schema.graphql @@ -908,7 +908,7 @@ enum Recurrance { input RecurrenceRuleInput { count: Int frequency: Frequency - weekdays: [WeekDays] + weekDays: [WeekDays] } enum Status { diff --git a/src/constants.ts b/src/constants.ts index 8256b67f37..b4490c7a34 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,6 @@ import { getEnvIssues, envSchema } from "./env"; import crypto from "crypto"; +import type { WeekDays } from "./types/generatedGraphQLTypes"; const issues = getEnvIssues(); let ENV = process.env; @@ -529,6 +530,15 @@ export const REDIS_PASSWORD = process.env.REDIS_PASSWORD; export const MILLISECONDS_IN_A_WEEK = 7 * 24 * 60 * 60 * 1000; +export const NORMAL_WEEKDAYS: WeekDays[] = [ + "SU", + "MO", + "TU", + "WE", + "TH", + "FR", + "SA", +]; export const RECURRING_EVENT_INSTANCES_MONTH_LIMIT = 6; export const RECURRENCE_FREQUENCIES = ["YEARLY", "MONTHLY", "WEEKLY", "DAILY"]; export const RECURRENCE_WEEKDAYS = [ diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index 948b41bb05..350cbe0c9b 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -18,7 +18,7 @@ export const generateRecurrenceRuleString = ( recurrenceEndDate?: Date ): string => { // destructure the rules - const { frequency, count, weekdays } = recurrenceRuleData; + const { frequency, count, weekDays } = recurrenceRuleData; // recurrence start date // (not necessarily the start date of the first recurring instance) @@ -33,7 +33,7 @@ export const generateRecurrenceRuleString = ( : ""; // string representing the days of the week the event would recur - const weekdaysString = weekdays?.length ? weekdays.join(",") : ""; + const weekdaysString = weekDays?.length ? weekDays.join(",") : ""; // initiate recurrence rule string let recurrenceRuleString = `DTSTART:${formattedRecurrenceStartDate}\nRRULE:FREQ=${frequency}`; diff --git a/src/typeDefs/inputs.ts b/src/typeDefs/inputs.ts index ab1dc00142..89fecd3d24 100644 --- a/src/typeDefs/inputs.ts +++ b/src/typeDefs/inputs.ts @@ -246,7 +246,7 @@ export const inputs = gql` input RecurrenceRuleInput { frequency: Frequency - weekdays: [WeekDays] + weekDays: [WeekDays] count: Int } diff --git a/src/types/generatedGraphQLTypes.ts b/src/types/generatedGraphQLTypes.ts index b61092127c..b944e6e9ef 100644 --- a/src/types/generatedGraphQLTypes.ts +++ b/src/types/generatedGraphQLTypes.ts @@ -1677,7 +1677,7 @@ export type Recurrance = export type RecurrenceRuleInput = { count?: InputMaybe; frequency?: InputMaybe; - weekdays?: InputMaybe>>; + weekDays?: InputMaybe>>; }; export type Status = diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 5d2280ed4b..6ae4f99bbf 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -11,6 +11,7 @@ import { import { LENGTH_VALIDATION_ERROR, + NORMAL_WEEKDAYS, ORGANIZATION_NOT_AUTHORIZED_ERROR, ORGANIZATION_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR, @@ -403,6 +404,10 @@ describe("resolvers -> Mutation -> createEvent", () => { let startDate = new Date(); startDate = addMonths(startDate, 1); + const today = startDate.getDay(); + const nextDay = (today + 1) % 7; + + const weekDays = [NORMAL_WEEKDAYS[today], NORMAL_WEEKDAYS[nextDay]]; const args: MutationCreateEventArgs = { data: { @@ -422,8 +427,8 @@ describe("resolvers -> Mutation -> createEvent", () => { }, recurrenceRuleData: { frequency: "WEEKLY", - weekdays: ["TH", "SA"], - count: 5, + weekDays, + count: 10, }, }; @@ -471,7 +476,7 @@ describe("resolvers -> Mutation -> createEvent", () => { }).lean(); expect(recurringEvents).toBeDefined(); - expect(recurringEvents).toHaveLength(5); + expect(recurringEvents).toHaveLength(10); const attendeeExists = await EventAttendee.exists({ userId: testUser?._id, From c65faf7481f0fbd6f4bc5da73ecc7040bc7bb5e3 Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 9 Feb 2024 12:26:13 +0530 Subject: [PATCH 22/29] fix test --- src/constants.ts | 10 ---------- .../generateRecurrenceRuleString.ts | 6 +++--- tests/resolvers/Mutation/createEvent.spec.ts | 9 ++------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index b4490c7a34..8256b67f37 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,5 @@ import { getEnvIssues, envSchema } from "./env"; import crypto from "crypto"; -import type { WeekDays } from "./types/generatedGraphQLTypes"; const issues = getEnvIssues(); let ENV = process.env; @@ -530,15 +529,6 @@ export const REDIS_PASSWORD = process.env.REDIS_PASSWORD; export const MILLISECONDS_IN_A_WEEK = 7 * 24 * 60 * 60 * 1000; -export const NORMAL_WEEKDAYS: WeekDays[] = [ - "SU", - "MO", - "TU", - "WE", - "TH", - "FR", - "SA", -]; export const RECURRING_EVENT_INSTANCES_MONTH_LIMIT = 6; export const RECURRENCE_FREQUENCIES = ["YEARLY", "MONTHLY", "WEEKLY", "DAILY"]; export const RECURRENCE_WEEKDAYS = [ diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index 350cbe0c9b..baf80ab102 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -33,7 +33,7 @@ export const generateRecurrenceRuleString = ( : ""; // string representing the days of the week the event would recur - const weekdaysString = weekDays?.length ? weekDays.join(",") : ""; + const weekDaysString = weekDays?.length ? weekDays.join(",") : ""; // initiate recurrence rule string let recurrenceRuleString = `DTSTART:${formattedRecurrenceStartDate}\nRRULE:FREQ=${frequency}`; @@ -45,8 +45,8 @@ export const generateRecurrenceRuleString = ( // maximum number of instances to create recurrenceRuleString += `;COUNT=${count}`; } - if (weekdaysString) { - recurrenceRuleString += `;BYDAY=${weekdaysString}`; + if (weekDaysString) { + recurrenceRuleString += `;BYDAY=${weekDaysString}`; } return recurrenceRuleString; diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 6ae4f99bbf..2f6b10acd4 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -11,7 +11,6 @@ import { import { LENGTH_VALIDATION_ERROR, - NORMAL_WEEKDAYS, ORGANIZATION_NOT_AUTHORIZED_ERROR, ORGANIZATION_NOT_FOUND_ERROR, USER_NOT_FOUND_ERROR, @@ -404,10 +403,6 @@ describe("resolvers -> Mutation -> createEvent", () => { let startDate = new Date(); startDate = addMonths(startDate, 1); - const today = startDate.getDay(); - const nextDay = (today + 1) % 7; - - const weekDays = [NORMAL_WEEKDAYS[today], NORMAL_WEEKDAYS[nextDay]]; const args: MutationCreateEventArgs = { data: { @@ -427,7 +422,7 @@ describe("resolvers -> Mutation -> createEvent", () => { }, recurrenceRuleData: { frequency: "WEEKLY", - weekDays, + weekDays: ["TH", "SA"], count: 10, }, }; @@ -476,7 +471,7 @@ describe("resolvers -> Mutation -> createEvent", () => { }).lean(); expect(recurringEvents).toBeDefined(); - expect(recurringEvents).toHaveLength(10); + expect(recurringEvents.length).toBeGreaterThan(1); const attendeeExists = await EventAttendee.exists({ userId: testUser?._id, From 8cc123a74228797793e22403a909929956a68aea Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 9 Feb 2024 21:39:06 +0530 Subject: [PATCH 23/29] convert to UTC dates --- .../createRecurringEvent.ts | 20 +++++++++---------- src/utilities/convertToUTCDate.ts | 16 +++++++++++++++ tests/resolvers/Mutation/createEvent.spec.ts | 11 +++++++--- 3 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 src/utilities/convertToUTCDate.ts diff --git a/src/helpers/event/createEventHelpers/createRecurringEvent.ts b/src/helpers/event/createEventHelpers/createRecurringEvent.ts index 90603f35f5..1e751f47a7 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvent.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvent.ts @@ -8,7 +8,7 @@ import { createRecurrenceRule, generateRecurringEventInstances, } from "../recurringEventHelpers"; -import { format } from "date-fns"; +import { convertToUTCDate } from "../../../utilities/convertToUTCDate"; /** * This function creates the instances of a recurring event upto a certain date. @@ -49,10 +49,10 @@ export const createRecurringEvent = async ( data?.endDate ); - const formattedStartDate = format(data.startDate, "yyyy-MM-dd"); - let formattedEndDate = undefined; + const convertedStartDate = convertToUTCDate(data.startDate); + let convertedEndDate = null; if (data.endDate) { - formattedEndDate = format(data.endDate, "yyyy-MM-dd"); + convertedEndDate = convertToUTCDate(data.endDate); } // create a base recurring event first, based on which all the @@ -61,8 +61,8 @@ export const createRecurringEvent = async ( [ { ...data, - startDate: formattedStartDate, - endDate: formattedEndDate, + startDate: convertedStartDate, + endDate: convertedEndDate, recurring: true, isBaseRecurringEvent: true, creatorId: currentUserId, @@ -78,15 +78,15 @@ export const createRecurringEvent = async ( const [recurringInstanceDates, latestInstanceDate] = getRecurringInstanceDates( recurrenceRuleString, - data.startDate, - data.endDate + convertedStartDate, + convertedEndDate ); // create a recurrenceRule document that would contain the recurrence pattern const recurrenceRule = await createRecurrenceRule( recurrenceRuleString, - data.startDate, - data.endDate, + convertedStartDate, + convertedEndDate, organizationId.toString(), baseRecurringEvent[0]?._id.toString(), latestInstanceDate, diff --git a/src/utilities/convertToUTCDate.ts b/src/utilities/convertToUTCDate.ts new file mode 100644 index 0000000000..1cfba42acf --- /dev/null +++ b/src/utilities/convertToUTCDate.ts @@ -0,0 +1,16 @@ +/** + * This function converts the date to UTC. + * @param date - the date to be converted. + * @returns converted date. + */ + +export const convertToUTCDate = (date: Date): Date => { + const year = date.getFullYear(); + const month = date.getMonth(); + const day = date.getDate(); + + // Create a new date object with local year, month, day but at UTC midnight + const utcMidnight = new Date(Date.UTC(year, month, day, 0, 0, 0, 0)); + + return utcMidnight; +}; diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 2f6b10acd4..e67adfadf4 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -29,6 +29,7 @@ import { import { fail } from "assert"; import { addMonths, format } from "date-fns"; import { Frequency, RecurrenceRule } from "../../../src/models/RecurrenceRule"; +import { convertToUTCDate } from "../../../src/utilities/convertToUTCDate"; let testUser: TestUserType; let testOrganization: TestOrganizationType; let MONGOOSE_INSTANCE: typeof mongoose; @@ -296,8 +297,10 @@ describe("resolvers -> Mutation -> createEvent", () => { } ); - const startDate = new Date(); - const endDate = addMonths(startDate, 1); + let startDate = new Date(); + startDate = convertToUTCDate(startDate); + let endDate = addMonths(startDate, 1); + endDate = convertToUTCDate(endDate); const args: MutationCreateEventArgs = { data: { @@ -404,6 +407,8 @@ describe("resolvers -> Mutation -> createEvent", () => { let startDate = new Date(); startDate = addMonths(startDate, 1); + startDate = convertToUTCDate(startDate); + const args: MutationCreateEventArgs = { data: { organizationId: testOrganization?.id, @@ -471,7 +476,7 @@ describe("resolvers -> Mutation -> createEvent", () => { }).lean(); expect(recurringEvents).toBeDefined(); - expect(recurringEvents.length).toBeGreaterThan(1); + expect(recurringEvents).toHaveLength(10); const attendeeExists = await EventAttendee.exists({ userId: testUser?._id, From a173b97300e54b504cec97c13a6da455f18711e3 Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 10 Feb 2024 15:58:05 +0530 Subject: [PATCH 24/29] test commit with minor change --- .../createEventHelpers/createRecurringEvent.ts | 14 ++++++++------ .../getRecurringInstanceDates.ts | 12 ++++-------- tests/resolvers/Mutation/createEvent.spec.ts | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvent.ts b/src/helpers/event/createEventHelpers/createRecurringEvent.ts index 1e751f47a7..6b23c15653 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvent.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvent.ts @@ -75,13 +75,15 @@ export const createRecurringEvent = async ( // 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, latestInstanceDate] = - getRecurringInstanceDates( - recurrenceRuleString, - convertedStartDate, - convertedEndDate - ); + const recurringInstanceDates = getRecurringInstanceDates( + recurrenceRuleString, + convertedStartDate, + convertedEndDate + ); + // 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, diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts index ba63bcccda..fce3f09c28 100644 --- a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -10,9 +10,8 @@ import { RECURRING_EVENT_INSTANCES_MONTH_LIMIT } from "../../../constants"; * @param calendarDate - the last date of the current calendar month (To be used during query). * @remarks The following steps are followed: * 1. Limit the end date for instance creation. - * 2. Getting the date upto which we would generate instances during this operation (leaving the rest for dynamic generation). - * 3. Getting the dates for recurring instances. - * @returns The recurring instance dates and the date of last instance generated during this operation. + * 3. Get the dates for recurring instances. + * @returns Dates for recurring instances to be generated during this operation. */ export function getRecurringInstanceDates( @@ -20,7 +19,7 @@ export function getRecurringInstanceDates( recurrenceStartDate: Date, eventEndDate: Date | null, calendarDate: Date = recurrenceStartDate -): [Date[], Date] { +): Date[] { const limitEndDate = addMonths( calendarDate, RECURRING_EVENT_INSTANCES_MONTH_LIMIT @@ -42,8 +41,5 @@ export function getRecurringInstanceDates( true ); - const latestInstanceDate = - recurringInstanceDates[recurringInstanceDates.length - 1]; - - return [recurringInstanceDates, latestInstanceDate]; + return recurringInstanceDates; } diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index e67adfadf4..87be2b3c72 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -367,7 +367,7 @@ describe("resolvers -> Mutation -> createEvent", () => { }).lean(); expect(recurringEvents).toBeDefined(); - expect(recurringEvents.length).toBeGreaterThan(1); + expect(recurringEvents.length).toEqual(5); const attendeeExists = await EventAttendee.exists({ userId: testUser?._id, From 0f3fd2f3e775bdbfc7b94e74eaaaf18e294864e6 Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 10 Feb 2024 22:20:39 +0530 Subject: [PATCH 25/29] limit instance generation date according to the recurrence frequency --- src/constants.ts | 10 +- .../getRecurringInstanceDates.ts | 51 ++- tests/resolvers/Mutation/createEvent.spec.ts | 326 +++++++++++++++++- 3 files changed, 374 insertions(+), 13 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 8256b67f37..8745ce129a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -529,8 +529,16 @@ export const REDIS_PASSWORD = process.env.REDIS_PASSWORD; export const MILLISECONDS_IN_A_WEEK = 7 * 24 * 60 * 60 * 1000; -export const RECURRING_EVENT_INSTANCES_MONTH_LIMIT = 6; +// recurring event frequencies export const RECURRENCE_FREQUENCIES = ["YEARLY", "MONTHLY", "WEEKLY", "DAILY"]; + +// recurring instance generation date limit in years based on it's frequency +export const RECURRING_EVENT_INSTANCES_DAILY_LIMIT = 1; +export const RECURRING_EVENT_INSTANCES_WEEKLY_LIMIT = 2; +export const RECURRING_EVENT_INSTANCES_MONTHLY_LIMIT = 5; +export const RECURRING_EVENT_INSTANCES_YEARLY_LIMIT = 10; + +// recurring event days export const RECURRENCE_WEEKDAYS = [ "MONDAY", "TUESDAY", diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts index fce3f09c28..ee3a3df1d4 100644 --- a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -1,15 +1,21 @@ -import { addMonths } from "date-fns"; -import { rrulestr } from "rrule"; -import { RECURRING_EVENT_INSTANCES_MONTH_LIMIT } from "../../../constants"; +import { addYears } from "date-fns"; +import { Frequency, rrulestr } from "rrule"; +import type { RRule } from "rrule"; +import { + RECURRING_EVENT_INSTANCES_DAILY_LIMIT, + RECURRING_EVENT_INSTANCES_WEEKLY_LIMIT, + RECURRING_EVENT_INSTANCES_MONTHLY_LIMIT, + RECURRING_EVENT_INSTANCES_YEARLY_LIMIT, +} from "../../../constants"; /** * This function generates the dates of recurrence for the recurring event. * @param recurrenceRuleString - the rrule string for the recurrenceRule. * @param recurrenceStartDate - the starting date from which we want to generate instances. * @param eventEndDate - the end date of the event - * @param calendarDate - the last date of the current calendar month (To be used during query). + * @param calendarDate - the calendar date (To be used for dynamic instance generation during queries). * @remarks The following steps are followed: - * 1. Limit the end date for instance creation. + * 1. Get the date limit for instance generation based on its recurrence frequency. * 3. Get the dates for recurring instances. * @returns Dates for recurring instances to be generated during this operation. */ @@ -20,21 +26,44 @@ export function getRecurringInstanceDates( eventEndDate: Date | null, calendarDate: Date = recurrenceStartDate ): Date[] { - const limitEndDate = addMonths( + // get the rrule object + const recurrenceRuleObject: RRule = rrulestr(recurrenceRuleString); + + // get the recurrence frequency + const { freq: recurrenceFrequency } = recurrenceRuleObject.options; + + // set limitEndDate according to the recurrence frequency + let limitEndDate = addYears( calendarDate, - RECURRING_EVENT_INSTANCES_MONTH_LIMIT - // generate instances upto this many months ahead - // leave the rest for dynamic generation during queries + RECURRING_EVENT_INSTANCES_DAILY_LIMIT ); + if (recurrenceFrequency === Frequency.WEEKLY) { + limitEndDate = addYears( + calendarDate, + RECURRING_EVENT_INSTANCES_WEEKLY_LIMIT + ); + } else if (recurrenceFrequency === Frequency.MONTHLY) { + limitEndDate = addYears( + calendarDate, + RECURRING_EVENT_INSTANCES_MONTHLY_LIMIT + ); + } else if (recurrenceFrequency === Frequency.YEARLY) { + limitEndDate = addYears( + calendarDate, + RECURRING_EVENT_INSTANCES_YEARLY_LIMIT + ); + } + + // if the event has no endDate eventEndDate = eventEndDate || limitEndDate; + // the date upto which we would generate the instances in this operation const generateUptoDate = new Date( Math.min(eventEndDate.getTime(), limitEndDate.getTime()) ); - const recurrenceRuleObject = rrulestr(recurrenceRuleString); - + // get the dates of recurrence const recurringInstanceDates = recurrenceRuleObject.between( recurrenceStartDate, generateUptoDate, diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index 87be2b3c72..cce79033ad 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -391,7 +391,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); }); - it(`creates the recurring event based on the recurrenceRuleData`, async () => { + it(`creates the daily recurring event based on the recurrenceRuleData`, async () => { await User.updateOne( { _id: testUser?._id, @@ -409,6 +409,114 @@ describe("resolvers -> Mutation -> createEvent", () => { startDate = convertToUTCDate(startDate); + const args: MutationCreateEventArgs = { + data: { + organizationId: testOrganization?.id, + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + startDate, + startTime: startDate.toUTCString(), + title: "newTitle", + recurrance: "ONCE", + }, + recurrenceRuleData: { + frequency: "DAILY", + count: 10, + }, + }; + + const context = { + userId: testUser?.id, + }; + const { createEvent: createEventResolver } = await import( + "../../../src/resolvers/Mutation/createEvent" + ); + + const createEventPayload = await createEventResolver?.({}, args, context); + + expect(createEventPayload).toEqual( + expect.objectContaining({ + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + title: "newTitle", + creatorId: testUser?._id, + admins: expect.arrayContaining([testUser?._id]), + organization: testOrganization?._id, + }) + ); + + const recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.DAILY, + startDate, + }); + + const baseRecurringEvent = await Event.findOne({ + isBaseRecurringEvent: true, + startDate: format(startDate, "yyyy-MM-dd"), + }); + + const recurringEvents = await Event.find({ + recurring: true, + isBaseRecurringEvent: false, + recurrenceRuleId: recurrenceRule?._id, + baseRecurringEventId: baseRecurringEvent?._id, + }).lean(); + + expect(recurringEvents).toBeDefined(); + expect(recurringEvents).toHaveLength(10); + + const attendeeExists = await EventAttendee.exists({ + userId: testUser?._id, + eventId: createEventPayload?._id, + }); + + expect(attendeeExists).toBeTruthy(); + + const updatedTestUser = await User.findOne({ + _id: testUser?._id, + }) + .select(["eventAdmin", "createdEvents", "registeredEvents"]) + .lean(); + + expect(updatedTestUser).toEqual( + expect.objectContaining({ + eventAdmin: expect.arrayContaining([createEventPayload?._id]), + createdEvents: expect.arrayContaining([createEventPayload?._id]), + registeredEvents: expect.arrayContaining([createEventPayload?._id]), + }) + ); + }); + + it(`creates the recurring event based on the recurrenceRuleData`, async () => { + await User.updateOne( + { + _id: testUser?._id, + }, + { + $push: { + createdOrganizations: testOrganization?._id, + joinedOrganizations: testOrganization?._id, + }, + } + ); + + let startDate = new Date(); + startDate = addMonths(startDate, 2); + + startDate = convertToUTCDate(startDate); + const args: MutationCreateEventArgs = { data: { organizationId: testOrganization?.id, @@ -500,6 +608,222 @@ describe("resolvers -> Mutation -> createEvent", () => { ); }); + it(`creates the monthly recurring event based on the recurrenceRuleData`, async () => { + await User.updateOne( + { + _id: testUser?._id, + }, + { + $push: { + createdOrganizations: testOrganization?._id, + joinedOrganizations: testOrganization?._id, + }, + } + ); + + let startDate = new Date(); + startDate = addMonths(startDate, 3); + + startDate = convertToUTCDate(startDate); + + const args: MutationCreateEventArgs = { + data: { + organizationId: testOrganization?.id, + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + startDate, + startTime: startDate.toUTCString(), + title: "newTitle", + recurrance: "ONCE", + }, + recurrenceRuleData: { + frequency: "MONTHLY", + count: 10, + }, + }; + + const context = { + userId: testUser?.id, + }; + const { createEvent: createEventResolver } = await import( + "../../../src/resolvers/Mutation/createEvent" + ); + + const createEventPayload = await createEventResolver?.({}, args, context); + + expect(createEventPayload).toEqual( + expect.objectContaining({ + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + title: "newTitle", + creatorId: testUser?._id, + admins: expect.arrayContaining([testUser?._id]), + organization: testOrganization?._id, + }) + ); + + const recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.MONTHLY, + startDate, + }); + + const baseRecurringEvent = await Event.findOne({ + isBaseRecurringEvent: true, + startDate: format(startDate, "yyyy-MM-dd"), + }); + + const recurringEvents = await Event.find({ + recurring: true, + isBaseRecurringEvent: false, + recurrenceRuleId: recurrenceRule?._id, + baseRecurringEventId: baseRecurringEvent?._id, + }).lean(); + + expect(recurringEvents).toBeDefined(); + expect(recurringEvents).toHaveLength(10); + + const attendeeExists = await EventAttendee.exists({ + userId: testUser?._id, + eventId: createEventPayload?._id, + }); + + expect(attendeeExists).toBeTruthy(); + + const updatedTestUser = await User.findOne({ + _id: testUser?._id, + }) + .select(["eventAdmin", "createdEvents", "registeredEvents"]) + .lean(); + + expect(updatedTestUser).toEqual( + expect.objectContaining({ + eventAdmin: expect.arrayContaining([createEventPayload?._id]), + createdEvents: expect.arrayContaining([createEventPayload?._id]), + registeredEvents: expect.arrayContaining([createEventPayload?._id]), + }) + ); + }); + + it(`creates the yearly recurring event based on the recurrenceRuleData`, async () => { + await User.updateOne( + { + _id: testUser?._id, + }, + { + $push: { + createdOrganizations: testOrganization?._id, + joinedOrganizations: testOrganization?._id, + }, + } + ); + + let startDate = new Date(); + startDate = addMonths(startDate, 4); + + startDate = convertToUTCDate(startDate); + + const args: MutationCreateEventArgs = { + data: { + organizationId: testOrganization?.id, + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + startDate, + startTime: startDate.toUTCString(), + title: "newTitle", + recurrance: "ONCE", + }, + recurrenceRuleData: { + frequency: "YEARLY", + count: 10, + }, + }; + + const context = { + userId: testUser?.id, + }; + const { createEvent: createEventResolver } = await import( + "../../../src/resolvers/Mutation/createEvent" + ); + + const createEventPayload = await createEventResolver?.({}, args, context); + + expect(createEventPayload).toEqual( + expect.objectContaining({ + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + title: "newTitle", + creatorId: testUser?._id, + admins: expect.arrayContaining([testUser?._id]), + organization: testOrganization?._id, + }) + ); + + const recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.YEARLY, + startDate, + }); + + const baseRecurringEvent = await Event.findOne({ + isBaseRecurringEvent: true, + startDate: format(startDate, "yyyy-MM-dd"), + }); + + const recurringEvents = await Event.find({ + recurring: true, + isBaseRecurringEvent: false, + recurrenceRuleId: recurrenceRule?._id, + baseRecurringEventId: baseRecurringEvent?._id, + }).lean(); + + expect(recurringEvents).toBeDefined(); + expect(recurringEvents).toHaveLength(10); + + const attendeeExists = await EventAttendee.exists({ + userId: testUser?._id, + eventId: createEventPayload?._id, + }); + + expect(attendeeExists).toBeTruthy(); + + const updatedTestUser = await User.findOne({ + _id: testUser?._id, + }) + .select(["eventAdmin", "createdEvents", "registeredEvents"]) + .lean(); + + expect(updatedTestUser).toEqual( + expect.objectContaining({ + eventAdmin: expect.arrayContaining([createEventPayload?._id]), + createdEvents: expect.arrayContaining([createEventPayload?._id]), + registeredEvents: expect.arrayContaining([createEventPayload?._id]), + }) + ); + }); + /* Commenting out this test because we are not using firebase notification anymore. it("should send a message when user and user.token exists", async () => { From 4cac65e65a810c6a36e82a452165711c5ac402fd Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 11 Feb 2024 15:46:46 +0530 Subject: [PATCH 26/29] adjust for different timezones --- .../createRecurringEvent.ts | 19 +-- .../generateRecurrenceRuleString.ts | 31 +++- .../generateRecurringEventInstances.ts | 7 +- .../getRecurringInstanceDates.ts | 2 +- src/resolvers/Mutation/createEvent.ts | 12 +- ...ertToUTCDate.ts => recurrenceDatesUtil.ts} | 17 +++ tests/resolvers/Mutation/createEvent.spec.ts | 143 ++++++++++++++++-- 7 files changed, 189 insertions(+), 42 deletions(-) rename src/utilities/{convertToUTCDate.ts => recurrenceDatesUtil.ts} (52%) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvent.ts b/src/helpers/event/createEventHelpers/createRecurringEvent.ts index 6b23c15653..c48b003a9a 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvent.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvent.ts @@ -8,7 +8,6 @@ import { createRecurrenceRule, generateRecurringEventInstances, } from "../recurringEventHelpers"; -import { convertToUTCDate } from "../../../utilities/convertToUTCDate"; /** * This function creates the instances of a recurring event upto a certain date. @@ -49,20 +48,14 @@ export const createRecurringEvent = async ( data?.endDate ); - const convertedStartDate = convertToUTCDate(data.startDate); - let convertedEndDate = null; - if (data.endDate) { - convertedEndDate = convertToUTCDate(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, - startDate: convertedStartDate, - endDate: convertedEndDate, + startDate: data.startDate, + endDate: data.endDate, recurring: true, isBaseRecurringEvent: true, creatorId: currentUserId, @@ -77,8 +70,8 @@ export const createRecurringEvent = async ( // to be generated in this operation (rest would be generated dynamically during query) const recurringInstanceDates = getRecurringInstanceDates( recurrenceRuleString, - convertedStartDate, - convertedEndDate + data.startDate, + data.endDate ); // get the date for the latest created instance @@ -87,8 +80,8 @@ export const createRecurringEvent = async ( // create a recurrenceRule document that would contain the recurrence pattern const recurrenceRule = await createRecurrenceRule( recurrenceRuleString, - convertedStartDate, - convertedEndDate, + data.startDate, + data.endDate, organizationId.toString(), baseRecurringEvent[0]?._id.toString(), latestInstanceDate, diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index baf80ab102..ae47178dcc 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -1,5 +1,6 @@ import { format } from "date-fns"; import type { RecurrenceRuleInput } from "../../../types/generatedGraphQLTypes"; +import { adjustForTimezoneOffset } from "../../../utilities/recurrenceDatesUtil"; /** * This function generates the recurrence rule (rrule) string. @@ -7,8 +8,8 @@ import type { RecurrenceRuleInput } from "../../../types/generatedGraphQLTypes"; * @param recurrenceStartDate - start date of recurrence. * @param recurrenceEndDate - end date of recurrence. * @remarks The following steps are followed: - * 1. Initiate an empty recurrenceRule string. - * 2. Add the recurrence rules one by one. + * 1. Adjust the start and end dates of recurrence for timezone offsets. + * 2. Get the recurrence rules and make a recurrenceRuleString. * @returns The recurrence rule string that would be used to create a valid rrule object. */ @@ -17,21 +18,39 @@ export const generateRecurrenceRuleString = ( recurrenceStartDate: Date, recurrenceEndDate?: Date ): string => { - // destructure the rules - const { frequency, count, weekDays } = recurrenceRuleData; + // adjust the dates according to the timezone offset + recurrenceStartDate = adjustForTimezoneOffset(recurrenceStartDate); + if (recurrenceEndDate) { + recurrenceEndDate = adjustForTimezoneOffset(recurrenceEndDate); + } // recurrence start date // (not necessarily the start date of the first recurring instance) - const formattedRecurrenceStartDate = format( + let formattedRecurrenceStartDate = format( recurrenceStartDate, "yyyyMMdd'T'HHmmss'Z'" ); + // format it to be UTC midnight + formattedRecurrenceStartDate = formattedRecurrenceStartDate.replace( + /T\d{6}Z/, + "T000000Z" + ); + // date upto which instances would be generated - const formattedRecurrenceEndDate = recurrenceEndDate + let formattedRecurrenceEndDate = recurrenceEndDate ? format(recurrenceEndDate, "yyyyMMdd'T'HHmmss'Z'") : ""; + // format it to be UTC midnight + formattedRecurrenceEndDate = formattedRecurrenceEndDate.replace( + /T\d{6}Z/, + "T000000Z" + ); + + // destructure the recurrence rules + const { frequency, count, weekDays } = recurrenceRuleData; + // string representing the days of the week the event would recur const weekDaysString = weekDays?.length ? weekDays.join(",") : ""; diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index f4308eee58..fc736bb490 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -1,5 +1,4 @@ import type mongoose from "mongoose"; -import { format } from "date-fns"; import type { InterfaceEvent } from "../../../models"; import { Event, EventAttendee, User } from "../../../models"; import type { EventInput } from "../../../types/generatedGraphQLTypes"; @@ -51,12 +50,10 @@ export const generateRecurringEventInstances = async ({ }: InterfaceGenerateRecurringInstances): Promise => { const recurringInstances: InterfaceRecurringEvent[] = []; recurringInstanceDates.map((date) => { - const formattedInstanceDate = format(date, "yyyy-MM-dd"); - const createdEventInstance = { ...data, - startDate: formattedInstanceDate, - endDate: formattedInstanceDate, + startDate: date, + endDate: date, recurring: true, isBaseRecurringEvent: false, recurrenceRuleId, diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts index ee3a3df1d4..f94b481257 100644 --- a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -16,7 +16,7 @@ import { * @param calendarDate - the calendar date (To be used for dynamic instance generation during queries). * @remarks The following steps are followed: * 1. Get the date limit for instance generation based on its recurrence frequency. - * 3. Get the dates for recurring instances. + * 2. Get the dates for recurring event instances. * @returns Dates for recurring instances to be generated during this operation. */ diff --git a/src/resolvers/Mutation/createEvent.ts b/src/resolvers/Mutation/createEvent.ts index fdf7b700e6..471cc7397e 100644 --- a/src/resolvers/Mutation/createEvent.ts +++ b/src/resolvers/Mutation/createEvent.ts @@ -126,10 +126,13 @@ export const createEvent: MutationResolvers["createEvent"] = async ( ); } + /* c8 ignore start */ if (session) { + // start a transaction session.startTransaction(); } + /* c8 ignore stop */ try { let createdEvent: InterfaceEvent; @@ -151,16 +154,23 @@ export const createEvent: MutationResolvers["createEvent"] = async ( ); } + /* c8 ignore start */ if (session) { + // commit transaction if everything's successful await session.commitTransaction(); } - // Returns the createdEvent. + /* c8 ignore stop */ return createdEvent; } catch (error) { + /* c8 ignore start */ if (session) { + // abort transaction if something fails await session.abortTransaction(); } + throw error; } + + /* c8 ignore stop */ }; diff --git a/src/utilities/convertToUTCDate.ts b/src/utilities/recurrenceDatesUtil.ts similarity index 52% rename from src/utilities/convertToUTCDate.ts rename to src/utilities/recurrenceDatesUtil.ts index 1cfba42acf..0fbcce6078 100644 --- a/src/utilities/convertToUTCDate.ts +++ b/src/utilities/recurrenceDatesUtil.ts @@ -14,3 +14,20 @@ export const convertToUTCDate = (date: Date): Date => { return utcMidnight; }; + +/** + * This function adjusts for the timezone offset. + * @param date - the date to be adjusted. + * @returns adjusted date. + */ +export const adjustForTimezoneOffset = (date: Date): Date => { + const timeZoneOffset = new Date().getTimezoneOffset(); + + /* c8 ignore start */ + if (timeZoneOffset > 0) { + date = new Date(date.getTime() + timeZoneOffset * 60 * 1000); + } + + /* c8 ignore stop */ + return date; +}; diff --git a/tests/resolvers/Mutation/createEvent.spec.ts b/tests/resolvers/Mutation/createEvent.spec.ts index cce79033ad..356c96a402 100644 --- a/tests/resolvers/Mutation/createEvent.spec.ts +++ b/tests/resolvers/Mutation/createEvent.spec.ts @@ -27,9 +27,9 @@ import { UnauthorizedError, } from "../../../src/libraries/errors"; import { fail } from "assert"; -import { addMonths, format } from "date-fns"; +import { addMonths } from "date-fns"; import { Frequency, RecurrenceRule } from "../../../src/models/RecurrenceRule"; -import { convertToUTCDate } from "../../../src/utilities/convertToUTCDate"; +import { convertToUTCDate } from "../../../src/utilities/recurrenceDatesUtil"; let testUser: TestUserType; let testOrganization: TestOrganizationType; let MONGOOSE_INSTANCE: typeof mongoose; @@ -299,8 +299,8 @@ describe("resolvers -> Mutation -> createEvent", () => { let startDate = new Date(); startDate = convertToUTCDate(startDate); - let endDate = addMonths(startDate, 1); - endDate = convertToUTCDate(endDate); + + const endDate = addMonths(startDate, 1); const args: MutationCreateEventArgs = { data: { @@ -356,7 +356,7 @@ describe("resolvers -> Mutation -> createEvent", () => { const baseRecurringEvent = await Event.findOne({ isBaseRecurringEvent: true, - startDate: format(startDate, "yyyy-MM-dd"), + startDate: startDate.toUTCString(), }); const recurringEvents = await Event.find({ @@ -391,7 +391,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); }); - it(`creates the daily recurring event based on the recurrenceRuleData`, async () => { + it(`creates the daily recurring event upto an end date based on the recurrenceRuleData`, async () => { await User.updateOne( { _id: testUser?._id, @@ -406,6 +406,117 @@ describe("resolvers -> Mutation -> createEvent", () => { let startDate = new Date(); startDate = addMonths(startDate, 1); + startDate = convertToUTCDate(startDate); + + const endDate = addMonths(startDate, 5); + + const args: MutationCreateEventArgs = { + data: { + organizationId: testOrganization?.id, + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + startDate, + startTime: startDate.toUTCString(), + endDate, + endTime: endDate.toUTCString(), + title: "newTitle", + recurrance: "ONCE", + }, + recurrenceRuleData: { + frequency: "DAILY", + }, + }; + + const context = { + userId: testUser?.id, + }; + const { createEvent: createEventResolver } = await import( + "../../../src/resolvers/Mutation/createEvent" + ); + + const createEventPayload = await createEventResolver?.({}, args, context); + + expect(createEventPayload).toEqual( + expect.objectContaining({ + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + title: "newTitle", + creatorId: testUser?._id, + admins: expect.arrayContaining([testUser?._id]), + organization: testOrganization?._id, + }) + ); + + const recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.DAILY, + startDate, + endDate, + }); + + const baseRecurringEvent = await Event.findOne({ + isBaseRecurringEvent: true, + startDate: startDate.toUTCString(), + endDate: endDate.toUTCString(), + }); + + const recurringEvents = await Event.find({ + recurring: true, + isBaseRecurringEvent: false, + recurrenceRuleId: recurrenceRule?._id, + baseRecurringEventId: baseRecurringEvent?._id, + }).lean(); + + expect(recurringEvents).toBeDefined(); + + const attendeeExists = await EventAttendee.exists({ + userId: testUser?._id, + eventId: createEventPayload?._id, + }); + + expect(attendeeExists).toBeTruthy(); + + const updatedTestUser = await User.findOne({ + _id: testUser?._id, + }) + .select(["eventAdmin", "createdEvents", "registeredEvents"]) + .lean(); + + expect(updatedTestUser).toEqual( + expect.objectContaining({ + eventAdmin: expect.arrayContaining([createEventPayload?._id]), + createdEvents: expect.arrayContaining([createEventPayload?._id]), + registeredEvents: expect.arrayContaining([createEventPayload?._id]), + }) + ); + }); + + it(`creates the daily recurring event with no end date based on the recurrenceRuleData`, async () => { + await User.updateOne( + { + _id: testUser?._id, + }, + { + $push: { + createdOrganizations: testOrganization?._id, + joinedOrganizations: testOrganization?._id, + }, + } + ); + + let startDate = new Date(); + startDate = addMonths(startDate, 2); startDate = convertToUTCDate(startDate); @@ -464,7 +575,7 @@ describe("resolvers -> Mutation -> createEvent", () => { const baseRecurringEvent = await Event.findOne({ isBaseRecurringEvent: true, - startDate: format(startDate, "yyyy-MM-dd"), + startDate: startDate.toUTCString(), }); const recurringEvents = await Event.find({ @@ -499,7 +610,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); }); - it(`creates the recurring event based on the recurrenceRuleData`, async () => { + it(`creates the weekly recurring event with no end date based on the recurrenceRuleData`, async () => { await User.updateOne( { _id: testUser?._id, @@ -513,7 +624,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); let startDate = new Date(); - startDate = addMonths(startDate, 2); + startDate = addMonths(startDate, 3); startDate = convertToUTCDate(startDate); @@ -573,7 +684,7 @@ describe("resolvers -> Mutation -> createEvent", () => { const baseRecurringEvent = await Event.findOne({ isBaseRecurringEvent: true, - startDate: format(startDate, "yyyy-MM-dd"), + startDate: startDate.toUTCString(), }); const recurringEvents = await Event.find({ @@ -608,7 +719,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); }); - it(`creates the monthly recurring event based on the recurrenceRuleData`, async () => { + it(`creates the monthly recurring event with no end date based on the recurrenceRuleData`, async () => { await User.updateOne( { _id: testUser?._id, @@ -622,7 +733,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); let startDate = new Date(); - startDate = addMonths(startDate, 3); + startDate = addMonths(startDate, 4); startDate = convertToUTCDate(startDate); @@ -681,7 +792,7 @@ describe("resolvers -> Mutation -> createEvent", () => { const baseRecurringEvent = await Event.findOne({ isBaseRecurringEvent: true, - startDate: format(startDate, "yyyy-MM-dd"), + startDate: startDate.toUTCString(), }); const recurringEvents = await Event.find({ @@ -716,7 +827,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); }); - it(`creates the yearly recurring event based on the recurrenceRuleData`, async () => { + it(`creates the yearly recurring event with no end date based on the recurrenceRuleData`, async () => { await User.updateOne( { _id: testUser?._id, @@ -730,7 +841,7 @@ describe("resolvers -> Mutation -> createEvent", () => { ); let startDate = new Date(); - startDate = addMonths(startDate, 4); + startDate = addMonths(startDate, 5); startDate = convertToUTCDate(startDate); @@ -789,7 +900,7 @@ describe("resolvers -> Mutation -> createEvent", () => { const baseRecurringEvent = await Event.findOne({ isBaseRecurringEvent: true, - startDate: format(startDate, "yyyy-MM-dd"), + startDate: startDate.toUTCString(), }); const recurringEvents = await Event.find({ From 8662fa1ee68c252f9d307dbe6af189c54001dc71 Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 11 Feb 2024 17:02:52 +0530 Subject: [PATCH 27/29] remove unnecessary code --- .../generateRecurrenceRuleString.ts | 47 ++++++------------- src/utilities/recurrenceDatesUtil.ts | 21 ++++----- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index ae47178dcc..9be0840843 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -1,6 +1,5 @@ -import { format } from "date-fns"; import type { RecurrenceRuleInput } from "../../../types/generatedGraphQLTypes"; -import { adjustForTimezoneOffset } from "../../../utilities/recurrenceDatesUtil"; +import { convertToRRuleDateString } from "../../../utilities/recurrenceDatesUtil"; /** * This function generates the recurrence rule (rrule) string. @@ -8,7 +7,7 @@ import { adjustForTimezoneOffset } from "../../../utilities/recurrenceDatesUtil" * @param recurrenceStartDate - start date of recurrence. * @param recurrenceEndDate - end date of recurrence. * @remarks The following steps are followed: - * 1. Adjust the start and end dates of recurrence for timezone offsets. + * 1. Get the date strings for start and end of recurrence. * 2. Get the recurrence rules and make a recurrenceRuleString. * @returns The recurrence rule string that would be used to create a valid rrule object. */ @@ -18,36 +17,16 @@ export const generateRecurrenceRuleString = ( recurrenceStartDate: Date, recurrenceEndDate?: Date ): string => { - // adjust the dates according to the timezone offset - recurrenceStartDate = adjustForTimezoneOffset(recurrenceStartDate); + // get the start date string for rrule's "DTSTART" property + const recurrenceStartDateString = + convertToRRuleDateString(recurrenceStartDate); + + // get the end date string for rrule's "UNTIL" property + let recurrenceEndDateString = ""; if (recurrenceEndDate) { - recurrenceEndDate = adjustForTimezoneOffset(recurrenceEndDate); + recurrenceEndDateString = convertToRRuleDateString(recurrenceEndDate); } - // recurrence start date - // (not necessarily the start date of the first recurring instance) - let formattedRecurrenceStartDate = format( - recurrenceStartDate, - "yyyyMMdd'T'HHmmss'Z'" - ); - - // format it to be UTC midnight - formattedRecurrenceStartDate = formattedRecurrenceStartDate.replace( - /T\d{6}Z/, - "T000000Z" - ); - - // date upto which instances would be generated - let formattedRecurrenceEndDate = recurrenceEndDate - ? format(recurrenceEndDate, "yyyyMMdd'T'HHmmss'Z'") - : ""; - - // format it to be UTC midnight - formattedRecurrenceEndDate = formattedRecurrenceEndDate.replace( - /T\d{6}Z/, - "T000000Z" - ); - // destructure the recurrence rules const { frequency, count, weekDays } = recurrenceRuleData; @@ -55,15 +34,17 @@ export const generateRecurrenceRuleString = ( const weekDaysString = weekDays?.length ? weekDays.join(",") : ""; // initiate recurrence rule string - let recurrenceRuleString = `DTSTART:${formattedRecurrenceStartDate}\nRRULE:FREQ=${frequency}`; + let recurrenceRuleString = `DTSTART:${recurrenceStartDateString}\nRRULE:FREQ=${frequency}`; - if (formattedRecurrenceEndDate) { - recurrenceRuleString += `;UNTIL=${formattedRecurrenceEndDate}`; + if (recurrenceEndDateString) { + recurrenceRuleString += `;UNTIL=${recurrenceEndDateString}`; } + if (count) { // maximum number of instances to create recurrenceRuleString += `;COUNT=${count}`; } + if (weekDaysString) { recurrenceRuleString += `;BYDAY=${weekDaysString}`; } diff --git a/src/utilities/recurrenceDatesUtil.ts b/src/utilities/recurrenceDatesUtil.ts index 0fbcce6078..1075692eb0 100644 --- a/src/utilities/recurrenceDatesUtil.ts +++ b/src/utilities/recurrenceDatesUtil.ts @@ -16,18 +16,17 @@ export const convertToUTCDate = (date: Date): Date => { }; /** - * This function adjusts for the timezone offset. - * @param date - the date to be adjusted. - * @returns adjusted date. + * This function converts the date to a valid rrule string argument. + * @param date - the date string to be converted. + * @returns converted date string. */ -export const adjustForTimezoneOffset = (date: Date): Date => { - const timeZoneOffset = new Date().getTimezoneOffset(); - /* c8 ignore start */ - if (timeZoneOffset > 0) { - date = new Date(date.getTime() + timeZoneOffset * 60 * 1000); - } +export const convertToRRuleDateString = (date: Date): string => { + let dateString = date.toISOString(); - /* c8 ignore stop */ - return date; + dateString = dateString.replace(/[-:]/g, ""); + + dateString = dateString.replace(/\.\d{3}/, ""); + + return dateString; }; From bef350c4a9555035c5d0718a95c85365c1bb4b99 Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 11 Feb 2024 22:08:38 +0530 Subject: [PATCH 28/29] remove unnecessary formatting --- src/helpers/event/createEventHelpers/createSingleEvent.ts | 8 +------- .../event/recurringEventHelpers/createRecurrenceRule.ts | 4 +--- .../recurringEventHelpers/generateRecurrenceRuleString.ts | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index 1b34489ee3..7276038a2e 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -3,7 +3,6 @@ import type { InterfaceEvent } from "../../../models"; import { Event, EventAttendee, User } from "../../../models"; import type { MutationCreateEventArgs } from "../../../types/generatedGraphQLTypes"; import { cacheEvents } from "../../../services/EventCache/cacheEvents"; -import { format } from "date-fns"; /** * This function generates a single non-recurring event. @@ -14,7 +13,7 @@ import { format } from "date-fns"; * 1. Create an event document. * 2. Associate the event with the user * 3. Cache the event. - * @returns The event generated. + * @returns The created event. */ export const createSingleEvent = async ( @@ -23,16 +22,11 @@ export const createSingleEvent = async ( organizationId: string, session: mongoose.ClientSession ): Promise => { - const formattedStartDate = format(args.data.startDate, "yyyy-MM-dd"); - const formattedEndDate = format(args.data.endDate, "yyyy-MM-dd"); - // create the single event const createdEvent = await Event.create( [ { ...args.data, - startDate: formattedStartDate, - endDate: formattedEndDate, creatorId: currentUserId, admins: [currentUserId], organization: organizationId, diff --git a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts index d7afe2ce03..1f0264c499 100644 --- a/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts +++ b/src/helpers/event/recurringEventHelpers/createRecurrenceRule.ts @@ -6,7 +6,6 @@ import { RECURRENCE_FREQUENCIES, RECURRENCE_WEEKDAYS, } from "../../../constants"; -import { format } from "date-fns"; /** * This function generates the recurrenceRule document. @@ -43,7 +42,6 @@ export const createRecurrenceRule = async ( } } - const formattedLatestInstanceDate = format(latestInstanceDate, "yyyy-MM-dd"); const frequency = RECURRENCE_FREQUENCIES[freq]; const recurrenceRule = await RecurrenceRule.create( @@ -57,7 +55,7 @@ export const createRecurrenceRule = async ( frequency, count: recurrenceRuleObject.options.count, weekDays, - latestInstanceDate: formattedLatestInstanceDate, + latestInstanceDate, }, ], { session } diff --git a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts index 9be0840843..86331a5b71 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurrenceRuleString.ts @@ -41,7 +41,7 @@ export const generateRecurrenceRuleString = ( } if (count) { - // maximum number of instances to create + // maximum number of instances to generate recurrenceRuleString += `;COUNT=${count}`; } From 34c13c78dadacab75cf3fb0f1c726e24944848ad Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 14 Feb 2024 09:43:01 +0530 Subject: [PATCH 29/29] change variable names --- .../createRecurringEvent.ts | 16 ++++---- .../createEventHelpers/createSingleEvent.ts | 12 +++--- .../generateRecurringEventInstances.ts | 40 ++++++++++--------- .../getRecurringInstanceDates.ts | 14 ++++--- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/helpers/event/createEventHelpers/createRecurringEvent.ts b/src/helpers/event/createEventHelpers/createRecurringEvent.ts index c48b003a9a..4c736d9646 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEvent.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEvent.ts @@ -12,7 +12,7 @@ import { /** * This function creates the instances of a recurring event upto a certain date. * @param args - payload of the createEvent mutation - * @param currentUserId - _id of the current user + * @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. @@ -26,7 +26,7 @@ import { export const createRecurringEvent = async ( args: MutationCreateEventArgs, - currentUserId: string, + creatorId: string, organizationId: string, session: mongoose.ClientSession ): Promise => { @@ -54,12 +54,10 @@ export const createRecurringEvent = async ( [ { ...data, - startDate: data.startDate, - endDate: data.endDate, recurring: true, isBaseRecurringEvent: true, - creatorId: currentUserId, - admins: [currentUserId], + creatorId, + admins: [creatorId], organization: organizationId, }, ], @@ -82,7 +80,7 @@ export const createRecurringEvent = async ( recurrenceRuleString, data.startDate, data.endDate, - organizationId.toString(), + organizationId, baseRecurringEvent[0]?._id.toString(), latestInstanceDate, session @@ -94,8 +92,8 @@ export const createRecurringEvent = async ( baseRecurringEventId: baseRecurringEvent[0]?._id.toString(), recurrenceRuleId: recurrenceRule?._id.toString(), recurringInstanceDates, - currentUserId: currentUserId.toString(), - organizationId: organizationId.toString(), + creatorId, + organizationId, session, }); diff --git a/src/helpers/event/createEventHelpers/createSingleEvent.ts b/src/helpers/event/createEventHelpers/createSingleEvent.ts index 7276038a2e..fe9799585d 100644 --- a/src/helpers/event/createEventHelpers/createSingleEvent.ts +++ b/src/helpers/event/createEventHelpers/createSingleEvent.ts @@ -7,7 +7,7 @@ import { cacheEvents } from "../../../services/EventCache/cacheEvents"; /** * This function generates a single non-recurring event. * @param args - the arguments provided for the createEvent mutation. - * @param currentUserId - _id of the current user. + * @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. @@ -18,7 +18,7 @@ import { cacheEvents } from "../../../services/EventCache/cacheEvents"; export const createSingleEvent = async ( args: MutationCreateEventArgs, - currentUserId: string, + creatorId: string, organizationId: string, session: mongoose.ClientSession ): Promise => { @@ -27,8 +27,8 @@ export const createSingleEvent = async ( [ { ...args.data, - creatorId: currentUserId, - admins: [currentUserId], + creatorId, + admins: [creatorId], organization: organizationId, }, ], @@ -39,7 +39,7 @@ export const createSingleEvent = async ( await EventAttendee.create( [ { - userId: currentUserId, + userId: creatorId, eventId: createdEvent[0]?._id, }, ], @@ -47,7 +47,7 @@ export const createSingleEvent = async ( ); await User.updateOne( { - _id: currentUserId, + _id: creatorId, }, { $push: { diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index fc736bb490..9578924b15 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -10,7 +10,7 @@ import { cacheEvents } from "../../../services/EventCache/cacheEvents"; * @param baseRecurringEventId - _id of the baseRecurringEvent. * @param recurrenceRuleId - _id of the recurrenceRule document containing the recurrence rule that the instances follow. * @param recurringInstanceDates - the dates of the recurring instances. - * @param currentUserId - _id of the current user. + * @param creatorId - _id of the creator. * @param organizationId - _id of the current organization. * @remarks The following steps are followed: * 1. Generate the instances for each provided date. @@ -25,7 +25,7 @@ interface InterfaceGenerateRecurringInstances { baseRecurringEventId: string; recurrenceRuleId: string; recurringInstanceDates: Date[]; - currentUserId: string; + creatorId: string; organizationId: string; session: mongoose.ClientSession; } @@ -44,7 +44,7 @@ export const generateRecurringEventInstances = async ({ baseRecurringEventId, recurrenceRuleId, recurringInstanceDates, - currentUserId, + creatorId, organizationId, session, }: InterfaceGenerateRecurringInstances): Promise => { @@ -58,8 +58,8 @@ export const generateRecurringEventInstances = async ({ isBaseRecurringEvent: false, recurrenceRuleId, baseRecurringEventId, - creatorId: currentUserId, - admins: [currentUserId], + creatorId, + admins: [creatorId], organization: organizationId, }; @@ -74,29 +74,31 @@ export const generateRecurringEventInstances = async ({ // add eventattendee for each instance const eventAttendees = recurringEventInstances.map( (recurringEventInstance) => ({ - userId: currentUserId, + userId: creatorId, eventId: recurringEventInstance?._id.toString(), }) ); - await EventAttendee.insertMany(eventAttendees, { session }); - + // get event instances ids for updating user event fields to include generated instances const eventInstanceIds = recurringEventInstances.map((instance) => instance._id.toString() ); - // update user event fields to include generated instances - await User.updateOne( - { _id: currentUserId }, - { - $push: { - eventAdmin: { $each: eventInstanceIds }, - createdEvents: { $each: eventInstanceIds }, - registeredEvents: { $each: eventInstanceIds }, + // perform database operations + await Promise.all([ + EventAttendee.insertMany(eventAttendees, { session }), + User.updateOne( + { _id: creatorId }, + { + $push: { + eventAdmin: { $each: eventInstanceIds }, + createdEvents: { $each: eventInstanceIds }, + registeredEvents: { $each: eventInstanceIds }, + }, }, - }, - { session } - ); + { session } + ), + ]); // cache the instances await Promise.all( diff --git a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts index f94b481257..d23c584d51 100644 --- a/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts +++ b/src/helpers/event/recurringEventHelpers/getRecurringInstanceDates.ts @@ -13,7 +13,7 @@ import { * @param recurrenceRuleString - the rrule string for the recurrenceRule. * @param recurrenceStartDate - the starting date from which we want to generate instances. * @param eventEndDate - the end date of the event - * @param calendarDate - the calendar date (To be used for dynamic instance generation during queries). + * @param queryUptoDate - the limit date to query recurrenceRules (To be used for dynamic instance generation during queries). * @remarks The following steps are followed: * 1. Get the date limit for instance generation based on its recurrence frequency. * 2. Get the dates for recurring event instances. @@ -24,7 +24,7 @@ export function getRecurringInstanceDates( recurrenceRuleString: string, recurrenceStartDate: Date, eventEndDate: Date | null, - calendarDate: Date = recurrenceStartDate + queryUptoDate: Date = recurrenceStartDate ): Date[] { // get the rrule object const recurrenceRuleObject: RRule = rrulestr(recurrenceRuleString); @@ -33,24 +33,26 @@ export function getRecurringInstanceDates( const { freq: recurrenceFrequency } = recurrenceRuleObject.options; // set limitEndDate according to the recurrence frequency + // and queryUptoDate, which would default to recurrenceStartDate during createRecurringEvent mutation + // and have a specific value during queries let limitEndDate = addYears( - calendarDate, + queryUptoDate, RECURRING_EVENT_INSTANCES_DAILY_LIMIT ); if (recurrenceFrequency === Frequency.WEEKLY) { limitEndDate = addYears( - calendarDate, + queryUptoDate, RECURRING_EVENT_INSTANCES_WEEKLY_LIMIT ); } else if (recurrenceFrequency === Frequency.MONTHLY) { limitEndDate = addYears( - calendarDate, + queryUptoDate, RECURRING_EVENT_INSTANCES_MONTHLY_LIMIT ); } else if (recurrenceFrequency === Frequency.YEARLY) { limitEndDate = addYears( - calendarDate, + queryUptoDate, RECURRING_EVENT_INSTANCES_YEARLY_LIMIT ); }