Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…awa-api into action-items
  • Loading branch information
meetulr committed Feb 19, 2024
2 parents cb126c3 + bd1e6c5 commit 9c8dd4b
Show file tree
Hide file tree
Showing 27 changed files with 1,797 additions and 170 deletions.
25 changes: 20 additions & 5 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ type AuthData {

type CheckIn {
_id: ID!
allotedRoom: String
allotedSeat: String
createdAt: DateTime!
event: Event!
feedbackSubmitted: Boolean!
Expand All @@ -122,8 +120,6 @@ type CheckIn {
}

input CheckInInput {
allotedRoom: String
allotedSeat: String
eventId: ID!
userId: ID!
}
Expand Down Expand Up @@ -300,6 +296,19 @@ type Event {
updatedAt: DateTime!
}

type EventAttendee {
_id: ID!
checkInId: ID
createdAt: DateTime!
eventId: ID!
isCheckedIn: Boolean!
isCheckedOut: Boolean!
isInvited: Boolean!
isRegistered: Boolean!
updatedAt: DateTime!
userId: ID!
}

input EventAttendeeInput {
eventId: ID!
userId: ID!
Expand Down Expand Up @@ -587,6 +596,8 @@ type Mutation {
blockUser(organizationId: ID!, userId: ID!): User!
cancelMembershipRequest(membershipRequestId: ID!): MembershipRequest!
checkIn(data: CheckInInput!): CheckIn!
checkInEventAttendee(data: EventAttendeeInput!): EventAttendee!
checkOutEventAttendee(data: EventAttendeeInput!): EventAttendee!
createActionItem(actionItemCategoryId: ID!, data: CreateActionItemInput!): ActionItem!
createActionItemCategory(name: String!, organizationId: ID!): ActionItemCategory!
createAdmin(data: UserAndOrganizationInput!): User!
Expand All @@ -610,6 +621,7 @@ type Mutation {
deleteAgendaCategory(id: ID!): ID!
deleteDonationById(id: ID!): DeletePayload!
forgotPassword(data: ForgotPasswordData!): Boolean!
inviteEventAttendee(data: EventAttendeeInput!): EventAttendee!
joinPublicOrganization(organizationId: ID!): User!
leaveOrganization(organizationId: ID!): User!
likeComment(id: ID!): Comment
Expand All @@ -619,7 +631,8 @@ type Mutation {
otp(data: OTPInput!): OtpData!
recaptcha(data: RecaptchaVerification!): Boolean!
refreshToken(refreshToken: String!): ExtendSession!
registerForEvent(id: ID!): Event!
registerEventAttendee(data: EventAttendeeInput!): EventAttendee!
registerForEvent(id: ID!): EventAttendee!
rejectAdmin(id: ID!): Boolean!
rejectMembershipRequest(membershipRequestId: ID!): MembershipRequest!
removeActionItem(id: ID!): ActionItem!
Expand Down Expand Up @@ -931,6 +944,8 @@ type Query {
getDonationById(id: ID!): Donation!
getDonationByOrgId(orgId: ID!): [Donation]
getDonationByOrgIdConnection(first: Int, orgId: ID!, skip: Int, where: DonationWhereInput): [Donation!]!
getEventAttendee(eventId: ID!, userId: ID!): EventAttendee
getEventAttendeesByEventId(eventId: ID!): [EventAttendee]
getPlugins: [Plugin]
getlanguage(lang_code: String!): [Translation]
hasSubmittedFeedback(eventId: ID!, userId: ID!): Boolean
Expand Down
11 changes: 11 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ export const USER_ALREADY_REGISTERED_FOR_EVENT = {
PARAM: "user.alreadyRegistered",
};

export const USER_ALREADY_INVITED_FOR_EVENT = {
MESSAGE: "The user has already been invited for the event",
CODE: "user.alreadyInvited",
PARAM: "user.alreadyInvited",
};

export const USER_NOT_REGISTERED_FOR_EVENT = {
MESSAGE: "The user is not registered for the event",
CODE: "user.notRegistered",
Expand Down Expand Up @@ -545,6 +551,11 @@ export const RECURRING_EVENT_INSTANCES_WEEKLY_LIMIT = 2;
export const RECURRING_EVENT_INSTANCES_MONTHLY_LIMIT = 5;
export const RECURRING_EVENT_INSTANCES_YEARLY_LIMIT = 10;

// recurrence rules query date limit in years
// i.e. query limit date to find the pending recurrence patterns
// and then generate new instances ahead of this date
export const RECURRING_EVENT_INSTANCES_QUERY_LIMIT = 1;

// recurring event days
export const RECURRENCE_WEEKDAYS = [
"MONDAY",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { addDays, addYears } from "date-fns";
import { RecurrenceRule } from "../../../models/RecurrenceRule";
import { convertToUTCDate } from "../../../utilities/recurrenceDatesUtil";
import { Event } from "../../../models";
import {
generateRecurringEventInstances,
getRecurringInstanceDates,
} from "../recurringEventHelpers";
import { session } from "../../../db";
import type { Recurrance } from "../../../types/generatedGraphQLTypes";
import type { InterfaceRecurringEvent } from "../recurringEventHelpers/generateRecurringEventInstances";
import { RECURRING_EVENT_INSTANCES_QUERY_LIMIT } from "../../../constants";

/**
* This function creates the instances of a recurring event upto a certain date during queries.
* @param organizationId - _id of the organization the events belong to
* @remarks The following steps are followed:
* 1. Get the limit date upto which we would want to query the recurrenceRules and generate new instances.
* 2. Get the recurrence rules to be used for instance generation during this query.
* 3. For every recurrence rule found:
* - find the base recurring event to get the data to be used for new instance generation.
* - get the number of existing instances and how many more to generate based on the recurrenceRule's count (if specified).
* - generate new instances after their latestInstanceDates.
* - update the latestInstanceDate.
*/

export const createRecurringEventInstancesDuringQuery = async (
organizationId: string | undefined | null,
): Promise<void> => {
if (!organizationId) {
return;
}

// get the current calendar date in UTC midnight
const calendarDate = convertToUTCDate(new Date());
const queryUptoDate = addYears(
calendarDate,
RECURRING_EVENT_INSTANCES_QUERY_LIMIT,
);

// get the recurrenceRules
const recurrenceRules = await RecurrenceRule.find({
organizationId,
latestInstanceDate: { $lt: queryUptoDate },
}).lean();

await Promise.all(
recurrenceRules.map(async (recurrenceRule) => {
// find the baseRecurringEvent for the recurrenceRule
const baseRecurringEvent = await Event.find({
_id: recurrenceRule.baseRecurringEventId,
}).lean();

// get the data from the baseRecurringEvent
const {
_id: baseRecurringEventId,
recurrance,
...data
} = baseRecurringEvent[0];

// get the input data for the generateRecurringEventInstances function
const currentInputData: InterfaceRecurringEvent = {
...data,
organizationId: recurrenceRule.organizationId.toString(),
recurrance: recurrance as Recurrance,
};

// get the properties from recurrenceRule
const {
_id: recurrenceRuleId,
latestInstanceDate,
recurrenceRuleString,
endDate: recurrenceEndDate,
count: totalInstancesCount,
} = recurrenceRule;

// get the date from which new instances would be generated
const currentRecurrenceStartDate = addDays(latestInstanceDate, 1);

// get the dates for recurrence
let recurringInstanceDates = getRecurringInstanceDates(
recurrenceRuleString,
currentRecurrenceStartDate,
recurrenceEndDate,
queryUptoDate,
);

// find out how many instances following the recurrence rule already exist and how many more to generate
if (totalInstancesCount) {
const totalExistingInstances = await Event.countDocuments({
recurrenceRuleId,
});

const remainingInstances = totalInstancesCount - totalExistingInstances;

recurringInstanceDates = recurringInstanceDates.slice(
0,
Math.min(recurringInstanceDates.length, remainingInstances),
);
}

/* c8 ignore start */
if (session) {
// start a transaction
session.startTransaction();
}

/* c8 ignore stop */
try {
if (recurringInstanceDates && recurringInstanceDates.length) {
const updatedLatestRecurringInstanceDate =
recurringInstanceDates[recurringInstanceDates.length - 1];

// update the latestInstanceDate of the recurrenceRule
await RecurrenceRule.updateOne(
{
_id: recurrenceRuleId,
},
{
latestInstanceDate: updatedLatestRecurringInstanceDate,
},
{ session },
);

// generate recurring event instances
await generateRecurringEventInstances({
data: currentInputData,
baseRecurringEventId: baseRecurringEventId.toString(),
recurrenceRuleId: recurrenceRuleId.toString(),
recurringInstanceDates,
creatorId: baseRecurringEvent[0].creatorId.toString(),
organizationId,
session,
});
}

/* c8 ignore start */
if (session) {
// commit transaction if everything's successful
await session.commitTransaction();
}
} catch (error) {
if (session) {
// abort transaction if something fails
await session.abortTransaction();
}

throw error;
}

/* c8 ignore stop */
}),
);
};
1 change: 1 addition & 0 deletions src/helpers/event/createEventHelpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { createSingleEvent } from "./createSingleEvent";
export { createRecurringEvent } from "./createRecurringEvent";
export { createRecurringEventInstancesDuringQuery } from "./createRecurringEventInstancesDuringQuery";
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,24 @@ import { cacheEvents } from "../../../services/EventCache/cacheEvents";
*/

interface InterfaceGenerateRecurringInstances {
data: EventInput;
data: InterfaceRecurringEvent;
baseRecurringEventId: string;
recurrenceRuleId: string;
recurringInstanceDates: Date[];
creatorId: string;
organizationId: string;
status?: string;
session: mongoose.ClientSession;
}

interface InterfaceRecurringEvent extends EventInput {
isBaseRecurringEvent: boolean;
recurrenceRuleId: string;
baseRecurringEventId: string;
creatorId: string;
admins: string[];
organization: string;
export interface InterfaceRecurringEvent extends EventInput {
isBaseRecurringEvent?: boolean;
recurrenceRuleId?: string;
baseRecurringEventId?: string;
creatorId?: string;
admins?: string[];
organization?: string;
status?: string;
}

export const generateRecurringEventInstances = async ({
Expand All @@ -49,7 +51,7 @@ export const generateRecurringEventInstances = async ({
session,
}: InterfaceGenerateRecurringInstances): Promise<InterfaceEvent> => {
const recurringInstances: InterfaceRecurringEvent[] = [];
recurringInstanceDates.map((date) => {
recurringInstanceDates.map((date): void => {
const createdEventInstance = {
...data,
startDate: date,
Expand All @@ -59,8 +61,9 @@ export const generateRecurringEventInstances = async ({
recurrenceRuleId,
baseRecurringEventId,
creatorId,
admins: [creatorId],
admins: data.admins && data.admins.length ? data.admins : [creatorId],
organization: organizationId,
status: data.status,
};

recurringInstances.push(createdEventInstance);
Expand Down
10 changes: 0 additions & 10 deletions src/models/CheckIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ export interface InterfaceCheckIn {
_id: Types.ObjectId;
eventAttendeeId: PopulatedDoc<InterfaceEventAttendee & Document>;
time: Date;
allotedRoom: string;
allotedSeat: string;
feedbackSubmitted: boolean;
createdAt: Date;
updatedAt: Date;
Expand All @@ -33,14 +31,6 @@ const checkInSchema = new Schema(
required: true,
default: Date.now,
},
allotedRoom: {
type: String,
required: false,
},
allotedSeat: {
type: String,
required: false,
},
feedbackSubmitted: {
type: Boolean,
required: true,
Expand Down
28 changes: 28 additions & 0 deletions src/models/EventAttendee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export interface InterfaceEventAttendee {
userId: PopulatedDoc<InterfaceUser & Document>;
eventId: PopulatedDoc<InterfaceEvent & Document>;
checkInId: PopulatedDoc<InterfaceCheckIn & Document> | null;
isInvited: boolean;
isRegistered: boolean;
isCheckedIn: boolean;
isCheckedOut: boolean;
}

const eventAttendeeSchema = new Schema({
Expand All @@ -29,6 +33,30 @@ const eventAttendeeSchema = new Schema({
default: null,
ref: "CheckIn",
},

isInvited: {
type: Boolean,
default: false,
required: true,
},

isRegistered: {
type: Boolean,
default: false,
required: true,
},

isCheckedIn: {
type: Boolean,
default: false,
required: true,
},

isCheckedOut: {
type: Boolean,
default: false,
required: true,
},
});

eventAttendeeSchema.index({ userId: 1, eventId: 1 }, { unique: true });
Expand Down
Loading

0 comments on commit 9c8dd4b

Please sign in to comment.