Skip to content

Commit

Permalink
Added Check out feature
Browse files Browse the repository at this point in the history
  • Loading branch information
git-init-priyanshu committed Mar 29, 2024
1 parent e3dc8f0 commit 5eeda97
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 62 deletions.
19 changes: 15 additions & 4 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,16 @@ type CheckIn {
user: User!
}

input CheckInInput {
type CheckOut {
_id: ID!
createdAt: DateTime!
event: Event!
time: DateTime!
updatedAt: DateTime!
user: User!
}

input CheckInCheckOutInput {
eventId: ID!
userId: ID!
}
Expand Down Expand Up @@ -615,6 +624,7 @@ type Event {
type EventAttendee {
_id: ID!
checkInId: ID
checkOutId: ID
createdAt: DateTime!
eventId: ID!
isCheckedIn: Boolean!
Expand Down Expand Up @@ -995,9 +1005,8 @@ type Mutation {
blockPluginCreationBySuperadmin(blockUser: Boolean!, userId: ID!): AppUserProfile!
blockUser(organizationId: ID!, userId: ID!): User!
cancelMembershipRequest(membershipRequestId: ID!): MembershipRequest!
checkIn(data: CheckInInput!): CheckIn!
checkInEventAttendee(data: EventAttendeeInput!): EventAttendee!
checkOutEventAttendee(data: EventAttendeeInput!): EventAttendee!
checkIn(data: CheckInCheckOutInput!): CheckIn!
checkOut(data: CheckInCheckOutInput!): CheckOut!
createActionItem(actionItemCategoryId: ID!, data: CreateActionItemInput!): ActionItem!
createActionItemCategory(name: String!, organizationId: ID!): ActionItemCategory!
createAdmin(data: UserAndOrganizationInput!): AppUserProfile!
Expand Down Expand Up @@ -1367,6 +1376,8 @@ type Query {
getAgendaItem(id: ID!): AgendaItem
getAgendaSection(id: ID!): AgendaSection
getAllAgendaItems: [AgendaItem]
getAllCheckInsForEvent(eventId: ID!): [CheckIn]
getAllCheckOutsForEvent(eventId: ID!): [CheckOut]
getCommunityData: Community
getDonationById(id: ID!): Donation!
getDonationByOrgId(orgId: ID!): [Donation]
Expand Down
15 changes: 14 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,11 +600,17 @@ export const EVENT_VOLUNTEER_INVITE_USER_MISTMATCH = {
};

export const USER_ALREADY_CHECKED_IN = {
MESSAGE: "The user has already been checked for this event.",
MESSAGE: "The user has already been checked in for this event.",
CODE: "user.alreadyCheckedIn",
PARAM: "user.alreadyCheckedIn",
};

export const USER_ALREADY_CHECKED_OUT = {
MESSAGE: "The user has already been checked out for this event.",
CODE: "user.alreadyCheckedOut",
PARAM: "user.alreadyCheckedOut",
};

export const SAMPLE_ORGANIZATION_ALREADY_EXISTS = {
DESC: "Sample Organization was already generated",
CODE: "sampleOrganization.duplicate",
Expand Down Expand Up @@ -636,6 +642,13 @@ export const CUSTOM_FIELD_TYPE_MISSING = {
PARAM: "customField.isMissing",
};

export const ATTENDEE_NOT_FOUND= {
DESC: "Attendee not found",
CODE: "attendee.notFound",
MESSAGE: "attendee.notFound",
PARAM: "attendee",
};

export const PRELOGIN_IMAGERY_FIELD_EMPTY = {
MESSAGE: "Website name, website link and the website logo cannot be empty",
CODE: "preLoginImagery.empty",
Expand Down
6 changes: 0 additions & 6 deletions src/models/CheckIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export interface InterfaceCheckIn {
_id: Types.ObjectId;
eventAttendeeId: PopulatedDoc<InterfaceEventAttendee & Document>;
time: Date;
feedbackSubmitted: boolean;
createdAt: Date;
updatedAt: Date;
}
Expand All @@ -31,11 +30,6 @@ const checkInSchema = new Schema(
required: true,
default: Date.now,
},
feedbackSubmitted: {
type: Boolean,
required: true,
default: false,
},
},
{
timestamps: true,
Expand Down
53 changes: 53 additions & 0 deletions src/models/CheckOut.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
Schema,
model,
type PopulatedDoc,
type Types,
type Document,
models,
type Model,
} from "mongoose";
import { type InterfaceEventAttendee } from "./EventAttendee";
import { createLoggingMiddleware } from "../libraries/dbLogger";

export interface InterfaceCheckOut {
_id: Types.ObjectId;
eventAttendeeId: PopulatedDoc<InterfaceEventAttendee & Document>;
time: Date;
createdAt: Date;
updatedAt: Date;
}

const checkOutSchema = new Schema(
{
eventAttendeeId: {
type: Schema.Types.ObjectId,
ref: "EventAttendee",
required: true,
},
time: {
type: Date,
required: true,
default: Date.now,
},
},
{
timestamps: true,
},
);

// We will also create an index here for faster database querying
checkOutSchema.index({
eventAttendeeId: 1,
});

createLoggingMiddleware(checkOutSchema, "CheckOut");

const checkOutModel = (): Model<InterfaceCheckOut> =>
model<InterfaceCheckOut>("CheckOut", checkOutSchema);

// This syntax is needed to prevent Mongoose OverwriteModelError while running tests.

export const CheckOut = (models.CheckOut || checkOutModel()) as ReturnType<
typeof checkOutModel
>;
8 changes: 8 additions & 0 deletions src/models/EventAttendee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { Schema, model, models } from "mongoose";
import type { InterfaceUser } from "./User";
import type { InterfaceEvent } from "./Event";
import type { InterfaceCheckIn } from "./CheckIn";
import type { InterfaceCheckOut } from "./CheckOut";
import { createLoggingMiddleware } from "../libraries/dbLogger";

export interface InterfaceEventAttendee {
_id: Schema.Types.ObjectId;
userId: PopulatedDoc<InterfaceUser & Document>;
eventId: PopulatedDoc<InterfaceEvent & Document>;
checkInId: PopulatedDoc<InterfaceCheckIn & Document> | null;
checkOutId: PopulatedDoc<InterfaceCheckOut & Document> | null;
isInvited: boolean;
isRegistered: boolean;
isCheckedIn: boolean;
Expand All @@ -33,6 +35,12 @@ const eventAttendeeSchema = new Schema({
default: null,
ref: "CheckIn",
},
checkOutId: {
type: Schema.Types.ObjectId,
required: false,
default: null,
ref: "CheckOut",
},

isInvited: {
type: Boolean,
Expand Down
1 change: 1 addition & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from "./AgendaCategory";
export * from "./AgendaItem";
export * from "./AgendaSection";
export * from "./CheckIn";
export * from "./CheckOut";
export * from "./Comment";
export * from "./Community";
export * from "./DirectChat";
Expand Down
27 changes: 0 additions & 27 deletions src/resolvers/Mutation/checkIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,6 @@ import type { MutationResolvers } from "../../types/generatedGraphQLTypes";
* 2. Checks if the current user is authorized to perform the check-in operation.
* 3. Checks if the attendee is already registered for the event. If so, updates the check-in status and isCheckedIn.
* 4. Checks if the attendee is not already checked in for the event then creates a new check-in entry and create new eventAttendee with chechInId and isCheckedIn.
*/

/**
* Handles the check-in process for event attendees.
*
* This resolver function allows event admins or superadmins to check-in attendees for a specific event.
* It verifies the existence of the current user, the event, and the attendee to be checked in,
* and ensures proper authorization before performing the check-in operation.
*
* @param _parent - The parent resolver.
* @param args - Arguments containing data for the check-in, including the eventId, userId, allotedSeat, and allotedRoom.
* @param context - Context object containing user authentication and request information.
* @returns The check-in data if successful.
* @throws NotFoundError if the current user, event, or attendee is not found.
* @throws UnauthorizedError if the current user lacks authorization to perform the check-in operation.
* @throws ConflictError if the attendee is already checked in for the event.
* @remarks
* The function performs the following checks and operations:
* 1. Verifies the existence of the current user, event, and attendee.
* 2. Checks if the current user is authorized to perform the check-in operation.
* 3. Checks if the attendee is already registered for the event. If so, updates the check-in status and isCheckedIn.
* 4. Checks if the attendee is not already checked in for the event then creates a new check-in entry and create new eventAttendee with chechInId and isCheckedIn.
*/

export const checkIn: MutationResolvers["checkIn"] = async (
Expand Down Expand Up @@ -181,9 +157,6 @@ export const checkIn: MutationResolvers["checkIn"] = async (
checkInId: checkIn._id,
},
);
// attendeeData.isCheckedIn = true;
// attendeeData.checkInId = checkIn._id;
// await attendeeData.save();

return checkIn.toObject();
};
150 changes: 150 additions & 0 deletions src/resolvers/Mutation/checkOut.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {
EVENT_NOT_FOUND_ERROR,
USER_NOT_AUTHORIZED_ERROR,
USER_NOT_FOUND_ERROR,
ATTENDEE_NOT_FOUND,
USER_NOT_CHECKED_IN,
USER_ALREADY_CHECKED_OUT,
} from "../../constants";
import type { MutationResolvers } from "../../types/generatedGraphQLTypes";
import { errors, requestContext } from "../../libraries";
import type { InterfaceEvent } from "../../models";
import { User, Event, EventAttendee, CheckOut } from "../../models";
import { findEventsInCache } from "../../services/EventCache/findEventInCache";
import { cacheEvents } from "../../services/EventCache/cacheEvents";
import { Types } from "mongoose";

/**
* Handles the check-in process for event attendees.
*
* This resolver function allows event admins or superadmins to check-in attendees for a specific event.
* It verifies the existence of the current user, the event, and the attendee to be checked in,
* and ensures proper authorization before performing the check-in operation.
*
* @param _parent - The parent resolver.
* @param args - Arguments containing data for the check-in, including the eventId, userId, allotedSeat, and allotedRoom.
* @param context - Context object containing user authentication and request information.
* @returns The check-in data if successful.
* @throws NotFoundError if the current user, event, or attendee is not found.
* @throws UnauthorizedError if the current user lacks authorization to perform the check-in operation.
* @throws ConflictError if the attendee is already checked in for the event.
* @remarks
* The function performs the following checks and operations:
* 1. Verifies the existence of the current user, event, and attendee.
* 2. Checks if the current user is authorized to perform the check-in operation.
* 3. Checks if the attendee is already registered for the event. If so, updates the check-in status and isCheckedIn.
* 4. Checks if the attendee is not already checked in for the event then creates a new check-in entry and create new eventAttendee with chechInId and isCheckedIn.
*/

export const checkOut: MutationResolvers["checkOut"] = async (
_parent,
args,
context,
) => {
const currentUser = await User.findOne({
_id: context.userId,
});

if (currentUser === null) {
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}

let currentEvent: InterfaceEvent | null;

const eventFoundInCache = await findEventsInCache([args.data.eventId]);

currentEvent = eventFoundInCache[0];

if (eventFoundInCache[0] === null) {
currentEvent = await Event.findOne({
_id: args.data.eventId,
}).lean();

if (currentEvent !== null) {
await cacheEvents([currentEvent]);
}
}

if (currentEvent === null) {
throw new errors.NotFoundError(
requestContext.translate(EVENT_NOT_FOUND_ERROR.MESSAGE),
EVENT_NOT_FOUND_ERROR.CODE,
EVENT_NOT_FOUND_ERROR.PARAM,
);
}

const isUserEventAdmin = currentEvent.admins.some(
(admin) =>
admin === context.userID || Types.ObjectId(admin).equals(context.userId),
);

if (!isUserEventAdmin && currentUser.userType !== "SUPERADMIN") {
throw new errors.UnauthorizedError(
requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE),
USER_NOT_AUTHORIZED_ERROR.CODE,
USER_NOT_AUTHORIZED_ERROR.PARAM,
);
}

const requestUser = await User.findOne({
_id: args.data.userId,
}).lean();

if (requestUser === null) {
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}

const attendeeData = await EventAttendee.findOne({
eventId: args.data.eventId,
userId: args.data.userId,
});

if (attendeeData === null) {
throw new errors.NotFoundError(
requestContext.translate(ATTENDEE_NOT_FOUND.MESSAGE),
ATTENDEE_NOT_FOUND.CODE,
ATTENDEE_NOT_FOUND.PARAM,
);
}

if (attendeeData.isCheckedIn === null) {
throw new errors.ConflictError(
requestContext.translate(USER_NOT_CHECKED_IN.MESSAGE),
USER_NOT_CHECKED_IN.CODE,
USER_NOT_CHECKED_IN.PARAM,
);
}

if (attendeeData.isCheckedOut) {
throw new errors.ConflictError(
requestContext.translate(USER_ALREADY_CHECKED_OUT.MESSAGE),
USER_ALREADY_CHECKED_OUT.CODE,
USER_ALREADY_CHECKED_OUT.PARAM,
);
}

const checkOut = await CheckOut.create({
eventAttendeeId: attendeeData._id,
});

await EventAttendee.updateOne(
{
eventId: args.data.eventId,
userId: currentEvent,
},
{
checkInId: checkOut._id,
},
);

return checkOut.toObject() as any;
};
2 changes: 2 additions & 0 deletions src/resolvers/Mutation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { blockPluginCreationBySuperadmin } from "./blockPluginCreationBySuperadm
import { blockUser } from "./blockUser";
import { cancelMembershipRequest } from "./cancelMembershipRequest";
import { checkIn } from "./checkIn";
import { checkOut } from "./checkOut";
import { createActionItem } from "./createActionItem";
import { createActionItemCategory } from "./createActionItemCategory";
import { createAdmin } from "./createAdmin";
Expand Down Expand Up @@ -144,6 +145,7 @@ export const Mutation: MutationResolvers = {
cancelMembershipRequest,
updateUserRoleInOrganization,
checkIn,
checkOut,
createMember,
createAdmin,
createActionItem,
Expand Down
Loading

0 comments on commit 5eeda97

Please sign in to comment.