Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Events attended support added with fetching recurring event query #2551

Merged
merged 32 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3bc9ece
eventsAttended added
duplixx May 31, 2024
07e2450
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Jun 7, 2024
8f9f6ec
events attended support added
duplixx Jun 11, 2024
dcc3866
eventsAttended added in user
duplixx Jun 18, 2024
2d9adca
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Jun 18, 2024
12992ee
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Jul 3, 2024
5978b13
changes implemented for eventsattended
duplixx Aug 3, 2024
f366dee
changes added in add Event Attendee
duplixx Aug 4, 2024
1d65fe0
added event supported
duplixx Aug 27, 2024
6597863
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Sep 1, 2024
9bace40
added eventsattended on expected reponse
duplixx Sep 25, 2024
02f4632
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Sep 28, 2024
a96ef24
added suggested changes
duplixx Oct 1, 2024
693f686
added expected payload
duplixx Oct 1, 2024
9637068
formatting done
duplixx Oct 1, 2024
e109418
added test cases
duplixx Oct 4, 2024
e770916
wip
duplixx Oct 5, 2024
440d651
added test cases for both queries
duplixx Oct 8, 2024
50c6c10
minor format changes
duplixx Oct 8, 2024
7bfad44
minor changes
duplixx Oct 8, 2024
30fbe61
minor
duplixx Oct 8, 2024
3ad2e4e
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Oct 8, 2024
87b6ffe
minor
duplixx Oct 8, 2024
83996d4
Merge branch 'Gsoc'24-duplixx' of https://github.com/duplixx/talawa-a…
duplixx Oct 8, 2024
f820db1
reverted the updated user error handling
duplixx Oct 9, 2024
b6ca09e
Merge branch 'develop' into Gsoc'24-duplixx
DMills27 Oct 20, 2024
1805f17
Merge branch 'develop' into Gsoc'24-duplixx
DMills27 Oct 22, 2024
a99866d
..
duplixx Oct 24, 2024
edd12de
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Oct 26, 2024
74a5414
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Nov 1, 2024
0876c39
fixed model error
duplixx Nov 1, 2024
dc0db19
Merge branch 'develop' into Gsoc'24-duplixx
duplixx Nov 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,7 @@ type Query {
directChatsMessagesByChatID(id: ID!): [DirectChatMessage]
event(id: ID!): Event
eventVolunteersByEvent(id: ID!): [EventVolunteer]
eventsAttendedByUser(id: ID, orderBy: EventOrderByInput): [Event]
eventsByOrganization(id: ID, orderBy: EventOrderByInput): [Event]
eventsByOrganizationConnection(first: Int, orderBy: EventOrderByInput, skip: Int, where: EventWhereInput): [Event!]!
fundsByOrganization(orderBy: FundOrderByInput, organizationId: ID!, where: FundWhereInput): [Fund]
Expand All @@ -1522,6 +1523,7 @@ type Query {
getNoteById(id: ID!): Note!
getPledgesByUserId(orderBy: PledgeOrderByInput, userId: ID!, where: PledgeWhereInput): [FundraisingCampaignPledge]
getPlugins: [Plugin]
getRecurringEvents(baseRecurringEventId: ID!): [Event]
getUserTag(id: ID!): UserTag
getUserTagAncestors(id: ID!): [UserTag]
getVenueByOrgId(first: Int, orderBy: VenueOrderByInput, orgId: ID!, skip: Int, where: VenueWhereInput): [Venue]
Expand Down Expand Up @@ -1827,6 +1829,7 @@ type User {
email: EmailAddress!
employmentStatus: EmploymentStatus
eventAdmin: [Event]
eventsAttended: [Event]
firstName: String!
duplixx marked this conversation as resolved.
Show resolved Hide resolved
gender: Gender
identifier: Int!
Expand Down
8 changes: 8 additions & 0 deletions src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface InterfaceUser {
mobile: string;
work: string;
};
eventsAttended: PopulatedDoc<InterfaceEvent & Document>[];

registeredEvents: PopulatedDoc<InterfaceEvent & Document>[];
status: string;
Expand Down Expand Up @@ -77,6 +78,7 @@ export interface InterfaceUser {
* @param phone - User's contact numbers (home, mobile, work).
* @param registeredEvents - Events the user has registered for.
* @param status - User's status (ACTIVE, BLOCKED, DELETED).
* @param eventsAttended - Events the user has attended.
* @param updatedAt - Timestamp of when the user was last updated.
*/
const userSchema = new Schema(
Expand Down Expand Up @@ -220,6 +222,12 @@ const userSchema = new Schema(
ref: "Event",
},
],
eventsAttended: [
{
type: Schema.Types.ObjectId,
ref: "Event",
},
],
status: {
type: String,
required: true,
Expand Down
19 changes: 18 additions & 1 deletion src/resolvers/Mutation/addEventAttendee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,22 @@ export const addEventAttendee: MutationResolvers["addEventAttendee"] = async (

await EventAttendee.create({ ...args.data });

return requestUser;
const updatedUser = await User.findByIdAndUpdate(
args.data.userId,
{
$push: {
eventsAttended: args.data.eventId,
},
},
{ new: true },
);

if (!updatedUser) {
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE),
USER_NOT_AUTHORIZED_ERROR.CODE,
USER_NOT_AUTHORIZED_ERROR.PARAM,
);
}
return updatedUser;
};
28 changes: 28 additions & 0 deletions src/resolvers/Query/eventsAttendedByUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { QueryResolvers } from "../../types/generatedGraphQLTypes";
import { Event } from "../../models";
import { getSort } from "./helperFunctions/getSort";

/**
* This query will fetch all the events for which user attended from the database.
* @param _parent-
* @param args - An object that contains `id` of the user and `orderBy`.
* @returns An object that contains the Event data.
* @remarks The query function uses `getSort()` function to sort the data in specified.
*/
export const eventsAttendedByUser: QueryResolvers["eventsAttendedByUser"] =
async (_parent, args) => {
const sort = getSort(args.orderBy);

return await Event.find({
registrants: {
$elemMatch: {
userId: args.id,
status: "ACTIVE",
},
},
})
.sort(sort)
.populate("creatorId", "-password")
.populate("admins", "-password")
.lean();
};
25 changes: 25 additions & 0 deletions src/resolvers/Query/getRecurringEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { QueryResolvers } from "../../types/generatedGraphQLTypes";
import { Event } from "../../models";

/**
* This query will fetch all the events with the same BaseRecurringEventId from the database.
* @param _parent -
* @param args - An object that contains `baseRecurringEventId` of the base recurring event.
* @returns An array of `Event` objects that are instances of the base recurring event.
*/

export const getRecurringEvents: QueryResolvers["getRecurringEvents"] = async (
_parent,
args,
) => {
try {
const recurringEvents = await Event.find({
baseRecurringEventId: args.baseRecurringEventId,
}).lean();

return recurringEvents;
} catch (error) {
console.error("Error fetching recurring events:", error);
throw error;
}
};
5 changes: 5 additions & 0 deletions src/resolvers/Query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ import { getEventAttendeesByEventId } from "./getEventAttendeesByEventId";
import { getVenueByOrgId } from "./getVenueByOrgId";
import { getAllNotesForAgendaItem } from "./getAllNotesForAgendaItem";
import { getNoteById } from "./getNoteById";
import { eventsAttendedByUser } from "./eventsAttendedByUser";
import { getRecurringEvents } from "./getRecurringEvents";

export const Query: QueryResolvers = {
actionItemsByEvent,
agendaCategory,
Expand Down Expand Up @@ -86,6 +89,7 @@ export const Query: QueryResolvers = {
getNoteById,
getlanguage,
getPlugins,
getRecurringEvents,
getUserTag,
getUserTagAncestors,
isSampleOrganization,
Expand All @@ -108,4 +112,5 @@ export const Query: QueryResolvers = {
getEventAttendee,
getEventAttendeesByEventId,
getVenueByOrgId,
eventsAttendedByUser,
};
2 changes: 2 additions & 0 deletions src/resolvers/Query/organizationsMemberConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export const organizationsMemberConnection: QueryResolvers["organizationsMemberC
registeredEvents: user.registeredEvents,
status: user.status,
updatedAt: user.updatedAt,
eventsAttended: user.eventsAttended,
}));
} else {
users = usersModel.docs.map((user) => ({
Expand Down Expand Up @@ -175,6 +176,7 @@ export const organizationsMemberConnection: QueryResolvers["organizationsMemberC
registeredEvents: user.registeredEvents,
status: user.status,
updatedAt: user.updatedAt,
eventsAttended: user.eventsAttended,
}));
}

Expand Down
4 changes: 4 additions & 0 deletions src/typeDefs/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ export const queries = gql`

getAllNotesForAgendaItem(agendaItemId: ID!): [Note]

getRecurringEvents(baseRecurringEventId: ID!): [Event]

advertisementsConnection(
after: String
before: String
Expand Down Expand Up @@ -200,5 +202,7 @@ export const queries = gql`
): [UserData]! @auth

venue(id: ID!): Venue

eventsAttendedByUser(id: ID, orderBy: EventOrderByInput): [Event]
}
`;
1 change: 1 addition & 0 deletions src/typeDefs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ export const types = gql`
phone: UserPhone
membershipRequests: [MembershipRequest]
registeredEvents: [Event]
eventsAttended: [Event]
pluginCreationAllowed: Boolean!
tagsAssignedWith(
after: String
Expand Down
17 changes: 17 additions & 0 deletions src/types/generatedGraphQLTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2284,6 +2284,7 @@ export type Query = {
directChatsMessagesByChatID?: Maybe<Array<Maybe<DirectChatMessage>>>;
event?: Maybe<Event>;
eventVolunteersByEvent?: Maybe<Array<Maybe<EventVolunteer>>>;
eventsAttendedByUser?: Maybe<Array<Maybe<Event>>>;
duplixx marked this conversation as resolved.
Show resolved Hide resolved
eventsByOrganization?: Maybe<Array<Maybe<Event>>>;
eventsByOrganizationConnection: Array<Event>;
fundsByOrganization?: Maybe<Array<Maybe<Fund>>>;
Expand All @@ -2305,6 +2306,7 @@ export type Query = {
getNoteById: Note;
getPledgesByUserId?: Maybe<Array<Maybe<FundraisingCampaignPledge>>>;
getPlugins?: Maybe<Array<Maybe<Plugin>>>;
getRecurringEvents?: Maybe<Array<Maybe<Event>>>;
getUserTag?: Maybe<UserTag>;
getUserTagAncestors?: Maybe<Array<Maybe<UserTag>>>;
getVenueByOrgId?: Maybe<Array<Maybe<Venue>>>;
Expand Down Expand Up @@ -2419,6 +2421,12 @@ export type QueryEventVolunteersByEventArgs = {
};


export type QueryEventsAttendedByUserArgs = {
id?: InputMaybe<Scalars['ID']['input']>;
orderBy?: InputMaybe<EventOrderByInput>;
};
duplixx marked this conversation as resolved.
Show resolved Hide resolved


export type QueryEventsByOrganizationArgs = {
id?: InputMaybe<Scalars['ID']['input']>;
orderBy?: InputMaybe<EventOrderByInput>;
Expand Down Expand Up @@ -2525,6 +2533,11 @@ export type QueryGetPledgesByUserIdArgs = {
};


export type QueryGetRecurringEventsArgs = {
baseRecurringEventId: Scalars['ID']['input'];
};


export type QueryGetUserTagArgs = {
id: Scalars['ID']['input'];
};
Expand Down Expand Up @@ -2939,6 +2952,7 @@ export type User = {
email: Scalars['EmailAddress']['output'];
employmentStatus?: Maybe<EmploymentStatus>;
eventAdmin?: Maybe<Array<Maybe<Event>>>;
eventsAttended?: Maybe<Array<Maybe<Event>>>;
firstName: Scalars['String']['output'];
gender?: Maybe<Gender>;
identifier: Scalars['Int']['output'];
Expand Down Expand Up @@ -4597,6 +4611,7 @@ export type QueryResolvers<ContextType = any, ParentType extends ResolversParent
directChatsMessagesByChatID?: Resolver<Maybe<Array<Maybe<ResolversTypes['DirectChatMessage']>>>, ParentType, ContextType, RequireFields<QueryDirectChatsMessagesByChatIdArgs, 'id'>>;
event?: Resolver<Maybe<ResolversTypes['Event']>, ParentType, ContextType, RequireFields<QueryEventArgs, 'id'>>;
eventVolunteersByEvent?: Resolver<Maybe<Array<Maybe<ResolversTypes['EventVolunteer']>>>, ParentType, ContextType, RequireFields<QueryEventVolunteersByEventArgs, 'id'>>;
eventsAttendedByUser?: Resolver<Maybe<Array<Maybe<ResolversTypes['Event']>>>, ParentType, ContextType, Partial<QueryEventsAttendedByUserArgs>>;
eventsByOrganization?: Resolver<Maybe<Array<Maybe<ResolversTypes['Event']>>>, ParentType, ContextType, Partial<QueryEventsByOrganizationArgs>>;
eventsByOrganizationConnection?: Resolver<Array<ResolversTypes['Event']>, ParentType, ContextType, Partial<QueryEventsByOrganizationConnectionArgs>>;
fundsByOrganization?: Resolver<Maybe<Array<Maybe<ResolversTypes['Fund']>>>, ParentType, ContextType, RequireFields<QueryFundsByOrganizationArgs, 'organizationId'>>;
Expand All @@ -4618,6 +4633,7 @@ export type QueryResolvers<ContextType = any, ParentType extends ResolversParent
getNoteById?: Resolver<ResolversTypes['Note'], ParentType, ContextType, RequireFields<QueryGetNoteByIdArgs, 'id'>>;
getPledgesByUserId?: Resolver<Maybe<Array<Maybe<ResolversTypes['FundraisingCampaignPledge']>>>, ParentType, ContextType, RequireFields<QueryGetPledgesByUserIdArgs, 'userId'>>;
getPlugins?: Resolver<Maybe<Array<Maybe<ResolversTypes['Plugin']>>>, ParentType, ContextType>;
getRecurringEvents?: Resolver<Maybe<Array<Maybe<ResolversTypes['Event']>>>, ParentType, ContextType, RequireFields<QueryGetRecurringEventsArgs, 'baseRecurringEventId'>>;
getUserTag?: Resolver<Maybe<ResolversTypes['UserTag']>, ParentType, ContextType, RequireFields<QueryGetUserTagArgs, 'id'>>;
getUserTagAncestors?: Resolver<Maybe<Array<Maybe<ResolversTypes['UserTag']>>>, ParentType, ContextType, RequireFields<QueryGetUserTagAncestorsArgs, 'id'>>;
getVenueByOrgId?: Resolver<Maybe<Array<Maybe<ResolversTypes['Venue']>>>, ParentType, ContextType, RequireFields<QueryGetVenueByOrgIdArgs, 'orgId'>>;
Expand Down Expand Up @@ -4722,6 +4738,7 @@ export type UserResolvers<ContextType = any, ParentType extends ResolversParentT
email?: Resolver<ResolversTypes['EmailAddress'], ParentType, ContextType>;
employmentStatus?: Resolver<Maybe<ResolversTypes['EmploymentStatus']>, ParentType, ContextType>;
eventAdmin?: Resolver<Maybe<Array<Maybe<ResolversTypes['Event']>>>, ParentType, ContextType>;
eventsAttended?: Resolver<Maybe<Array<Maybe<ResolversTypes['Event']>>>, ParentType, ContextType>;
firstName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
gender?: Resolver<Maybe<ResolversTypes['Gender']>, ParentType, ContextType>;
identifier?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
Expand Down
95 changes: 65 additions & 30 deletions tests/resolvers/Mutation/addEventAttendee.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,39 +153,41 @@ describe("resolvers -> Mutation -> addEventAttendee", () => {
});

it(`registers the request user for the event successfully and returns the request user`, async () => {
const eventOrganizationId = testEvent?.organization._id;
const userId = randomTestUser?._id;
if (testEvent?.organization) {
const eventOrganizationId = testEvent?.organization._id;
const userId = randomTestUser?._id;

await User.updateOne(
{ _id: userId },
{ $addToSet: { joinedOrganizations: eventOrganizationId } },
);

const args: MutationAddEventAttendeeArgs = {
data: {
userId: userId,
eventId: testEvent?._id ?? "",
},
};

const context = { userId: testUser?._id };
await User.updateOne(
{ _id: userId },
{ $addToSet: { joinedOrganizations: eventOrganizationId } },
);

const { addEventAttendee: addEventAttendeeResolver } = await import(
"../../../src/resolvers/Mutation/addEventAttendee"
);
const payload = await addEventAttendeeResolver?.({}, args, context);
const args: MutationAddEventAttendeeArgs = {
data: {
userId: userId,
eventId: testEvent?._id.toString() ?? "",
},
};

const requestUser = await User.findOne({
_id: userId?._id,
}).lean();
const context = { userId: testUser?._id };

const isUserRegistered = await EventAttendee.exists({
...args.data,
});
expect(payload).toEqual(requestUser);
expect(isUserRegistered).toBeTruthy();
const { addEventAttendee: addEventAttendeeResolver } = await import(
"../../../src/resolvers/Mutation/addEventAttendee"
);
const payload = await addEventAttendeeResolver?.({}, args, context);

expect(payload).toBeDefined();

const requestUser = await User.findOne({
_id: userId?._id,
}).lean();
expect(payload).toEqual(expect.objectContaining(requestUser));
const isUserRegistered = await EventAttendee.exists({
...args.data,
});
expect(isUserRegistered).toBeTruthy();
}
});

it(`throws UnauthorizedError if the requestUser is not a member of the organization`, async () => {
const { requestContext } = await import("../../../src/libraries");

Expand All @@ -199,11 +201,10 @@ describe("resolvers -> Mutation -> addEventAttendee", () => {
);

try {
// Create a test user who is not a member of the organization
const args: MutationAddEventAttendeeArgs = {
data: {
userId: testUser?._id,
eventId: testEvent!._id,
eventId: testEvent!._id.toString(),
},
};

Expand Down Expand Up @@ -291,4 +292,38 @@ describe("resolvers -> Mutation -> addEventAttendee", () => {
);
}
});
it("throws NotFoundError if the user is not found after update", async () => {
const { requestContext } = await import("../../../src/libraries");

const spy = vi
.spyOn(requestContext, "translate")
.mockImplementationOnce((message) => `Translated ${message}`);

const mockFindByIdAndUpdate = vi
.spyOn(User, "findByIdAndUpdate")
.mockResolvedValueOnce(null);

const args: MutationAddEventAttendeeArgs = {
data: {
userId: testUser?._id,
eventId: testEvent?._id.toString() ?? "",
},
};

const context = { userId: testUser?._id };

try {
const { addEventAttendee: addEventAttendeeResolver } = await import(
"../../../src/resolvers/Mutation/addEventAttendee"
);

await addEventAttendeeResolver?.({}, args, context);
} catch (error: unknown) {
expect((error as Error).message).toEqual(
`Translated ${USER_NOT_AUTHORIZED_ERROR.MESSAGE}`,
);
expect(spy).toHaveBeenLastCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE);
}
mockFindByIdAndUpdate.mockRestore();
});
});
Loading
Loading