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

Feature: Addition of Venue for Events #1763

Merged
merged 44 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7ef1b7c
added Venue model, added create, fetch and edit venue controllers
wingman47 Jan 30, 2024
bd0f02e
added delete venue controller
wingman47 Jan 30, 2024
526459f
added tests for create, edit, delete and fetch (venuesInOrganization)
wingman47 Jan 30, 2024
3a69a19
fixed type errors
wingman47 Jan 30, 2024
2228687
removed unnecessary changes
wingman47 Jan 30, 2024
3aee01f
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 1, 2024
e39cfde
removed venue property from Organization model, modified create, dele…
wingman47 Feb 1, 2024
a50e9a3
added field based query for venues
wingman47 Feb 1, 2024
0fe62bd
merge develop branch
wingman47 Feb 10, 2024
2c4467d
updated tests
wingman47 Feb 10, 2024
dfc0ae3
fixed lint
wingman47 Feb 10, 2024
47d1981
removed unused imports
wingman47 Feb 10, 2024
069ebec
fixed type imports
wingman47 Feb 10, 2024
7fc5548
fixed lint error
wingman47 Feb 10, 2024
c392313
fixed formatting
wingman47 Feb 10, 2024
bba70d0
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 12, 2024
ce938db
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 13, 2024
b3dd6d5
fixed formatting error
wingman47 Feb 13, 2024
53ef49b
increased code cov
wingman47 Feb 13, 2024
d4cc808
added required changes
wingman47 Feb 14, 2024
ec6fe8d
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 14, 2024
5ed2e92
removed requirement for orgId in editVenue
wingman47 Feb 14, 2024
e9e34d8
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 15, 2024
8617bcd
added query resolver for venue
wingman47 Feb 15, 2024
c6b6970
fixed import types
wingman47 Feb 15, 2024
4d3accb
fixed import
wingman47 Feb 15, 2024
6ba50ce
renamed organizationId to organization
wingman47 Feb 16, 2024
5a8f902
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 16, 2024
7cf7757
changes in editVenue
wingman47 Feb 16, 2024
ccf3e39
upgraded prettier to perform merge
wingman47 Feb 16, 2024
166702e
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 16, 2024
f5a24c4
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 17, 2024
4b76327
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 18, 2024
345e735
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 18, 2024
b554d49
Merge branch 'develop' into venue-CRUD
EshaanAgg Feb 19, 2024
2c6b86b
updated input fields
wingman47 Feb 19, 2024
55c267d
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 19, 2024
93384a6
Merge branch 'venue-CRUD' of https://github.com/wingman47/talawa-api …
wingman47 Feb 19, 2024
9893977
renamed field
wingman47 Feb 19, 2024
69adb9e
removed venue from event
wingman47 Feb 19, 2024
94cd929
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 20, 2024
49be8de
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Feb 24, 2024
c014682
renamed _id to id
wingman47 Feb 24, 2024
4033c6c
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
wingman47 Mar 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
30 changes: 30 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,14 @@ input DonationWhereInput {
name_of_user_starts_with: String
}

input EditVenueInput {
capacity: Int
description: String
file: String
id: ID!
name: String
}

enum EducationGrade {
GRADE_1
GRADE_2
Expand Down Expand Up @@ -913,9 +921,12 @@ type Mutation {
createSampleOrganization: Boolean!
createUserFamily(data: createUserFamilyInput!): UserFamily!
createUserTag(input: CreateUserTagInput!): UserTag
createVenue(data: VenueInput!): Venue
deleteAdvertisement(id: ID!): DeleteAdvertisementPayload
deleteAgendaCategory(id: ID!): ID!
deleteDonationById(id: ID!): DeletePayload!
deleteVenue(id: ID!): Venue
editVenue(data: EditVenueInput!): Venue
forgotPassword(data: ForgotPasswordData!): Boolean!
inviteEventAttendee(data: EventAttendeeInput!): EventAttendee!
joinPublicOrganization(organizationId: ID!): User!
Expand Down Expand Up @@ -1013,6 +1024,7 @@ type Organization {
updatedAt: DateTime!
userRegistrationRequired: Boolean!
userTags(after: String, before: String, first: PositiveInt, last: PositiveInt): UserTagsConnection
venues: [Venue]
visibleInSearch: Boolean!
}

Expand Down Expand Up @@ -1271,6 +1283,7 @@ type Query {
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]!
venue(id: ID!): Venue
}

input RecaptchaVerification {
Expand Down Expand Up @@ -1659,6 +1672,23 @@ type UsersConnectionEdge {
node: User!
}

type Venue {
_id: ID!
wingman47 marked this conversation as resolved.
Show resolved Hide resolved
capacity: Int!
description: String
imageUrl: URL
name: String!
organization: Organization!
}

input VenueInput {
capacity: Int!
description: String
file: String
name: String!
organizationId: ID!
}

enum WeekDays {
FR
MO
Expand Down
29 changes: 29 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ export const CHAT_NOT_FOUND_ERROR = {
MESSAGE: "chat.notFound",
PARAM: "chat",
};

export const VENUE_ALREADY_EXISTS_ERROR = {
DESC: "Venue already exists",
CODE: "venue.alreadyExists",
MESSAGE: "venue.alreadyExists",
PARAM: "venue",
};

export const VENUE_NOT_FOUND_ERROR = {
DESC: "Venue does not exist",
CODE: "venue.NotFound",
MESSAGE: "venue.NotFound",
PARAM: "venue",
};

export const COMMENT_NOT_FOUND_ERROR = {
DESC: "Comment not found",
CODE: "comment.notFound",
Expand Down Expand Up @@ -138,6 +153,20 @@ export const ORGANIZATION_NOT_FOUND_ERROR = {
MESSAGE: "organization.notFound",
PARAM: "organization",
};
export const VENUE_NAME_MISSING_ERROR = {
DESC: "Venue name not found",
CODE: "venueName.notFound",
MESSAGE: "venueName.notFound",
PARAM: "venueName",
};

export const VENUE_ALREADY_SCHEDULED = {
DESC: "Venue is already scheduled",
CODE: "venue.alreadySchduled",
MESSAGE: "venue.alreadySchduled",
PARAM: "venue",
};

export const ORGANIZATION_IMAGE_NOT_FOUND_ERROR = {
DESC: "OrganizationImage not found",
CODE: "organizationImage.notFound",
Expand Down
52 changes: 52 additions & 0 deletions src/models/Venue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Schema, model, models } from "mongoose";
import type { Model, Types, PopulatedDoc } from "mongoose";
import type { InterfaceOrganization } from "./Organization";

export interface InterfaceVenue {
_id: Types.ObjectId;
name: string;
description: string | undefined | null;
capacity: number;
imageUrl: string | undefined | null;
organization: PopulatedDoc<InterfaceOrganization & Document>;
}

/**
* This describes the schema for a Venue that corresponds to InterfaceVenue document.
* @param name - Name of the venue.
* @param description - Description of the venue.
* @param capacity - Maximum capacity of the venue.
* @param imageUrl - Image URL(if attached) of the venue.
* @param organization - Organization in which the venue belongs.
*/

const venueSchema = new Schema({
name: {
type: String,
required: true,
},
description: {
type: String,
},
capacity: {
type: Number,
required: true,
},
imageUrl: {
type: String,
required: false,
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization",
required: true,
},
});
wingman47 marked this conversation as resolved.
Show resolved Hide resolved

const venueModel = (): Model<InterfaceVenue> =>
model<InterfaceVenue>("Venue", venueSchema);

// This syntax is needed to prevent Mongoose OverwriteModelError while running tests.
export const Venue = (models.Venue || venueModel()) as ReturnType<
typeof venueModel
>;
1 change: 1 addition & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ export * from "./PluginField";
export * from "./Post";
export * from "./SampleData";
export * from "./TagUser";
export * from "./Venue";
export * from "./User";
116 changes: 116 additions & 0 deletions src/resolvers/Mutation/createVenue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Organization, User } from "../../models";
import type { MutationResolvers } from "../../types/generatedGraphQLTypes";
import {
ORGANIZATION_NOT_FOUND_ERROR,
VENUE_ALREADY_EXISTS_ERROR,
USER_NOT_FOUND_ERROR,
VENUE_NAME_MISSING_ERROR,
ORGANIZATION_NOT_AUTHORIZED_ERROR,
} from "../../constants";
import { errors, requestContext } from "../../libraries";
import { Venue } from "../../models/Venue";
import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage";
/**
* This function enables to create a venue in an organization.
* @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. Whether the user is admin or superadmin or not
* 4. If the venue name is missing
* 5. If the same venue already exists in an organization
* @returns Created venue
*/

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

// Checks whether currentUser with _id == context.userId exists.
if (currentUser === null) {
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}

const organization = await Organization.findOne({
_id: args.data.organizationId,
});

// Checks whether organization exists.
if (!organization) {
throw new errors.NotFoundError(
requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE),
ORGANIZATION_NOT_FOUND_ERROR.CODE,
ORGANIZATION_NOT_FOUND_ERROR.PARAM,
);
}

// Checks Whether the user is admin or superadmin or not
if (
!(
organization.admins?.some((admin) => admin._id.equals(context.userId)) ||
currentUser.userType == "SUPERADMIN"
)
) {
throw new errors.UnauthorizedError(
requestContext.translate(ORGANIZATION_NOT_AUTHORIZED_ERROR.MESSAGE),
ORGANIZATION_NOT_AUTHORIZED_ERROR.CODE,
ORGANIZATION_NOT_AUTHORIZED_ERROR.PARAM,
);
}

// Check if the venue name provided is empty string
if (!args.data?.name ?? "") {
throw new errors.InputValidationError(
requestContext.translate(VENUE_NAME_MISSING_ERROR.MESSAGE),
VENUE_NAME_MISSING_ERROR.CODE,
VENUE_NAME_MISSING_ERROR.PARAM,
);
}

// Check if a venue with the same organizationId and name exists
const existingVenue = await Venue.findOne({
name: args.data.name,
organization: args.data.organizationId,
});

if (existingVenue) {
throw new errors.ConflictError(
requestContext.translate(VENUE_ALREADY_EXISTS_ERROR.MESSAGE),
VENUE_ALREADY_EXISTS_ERROR.CODE,
VENUE_ALREADY_EXISTS_ERROR.PARAM,
);
}

let uploadImageFileName = null;

if (args.data?.file) {
const dataUrlPrefix = "data:";
if (args.data.file.startsWith(dataUrlPrefix + "image/")) {
uploadImageFileName = await uploadEncodedImage(args.data.file, null);
}

Check warning on line 101 in src/resolvers/Mutation/createVenue.ts

View check run for this annotation

Codecov / codecov/patch

src/resolvers/Mutation/createVenue.ts#L101

Added line #L101 was not covered by tests
}

const newVenue = await Venue.create({
...args.data,
organization: organization._id,
imageUrl: uploadImageFileName
? `${context.apiRootUrl}${uploadImageFileName}`
: null,
});

return {
...newVenue.toObject(),
organization: organization.toObject(),
};
};
87 changes: 87 additions & 0 deletions src/resolvers/Mutation/deleteVenue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Organization, User } from "../../models";
import type { MutationResolvers } from "../../types/generatedGraphQLTypes";
import {
ORGANIZATION_NOT_FOUND_ERROR,
USER_NOT_FOUND_ERROR,
ORGANIZATION_NOT_AUTHORIZED_ERROR,
VENUE_NOT_FOUND_ERROR,
} from "../../constants";
import { errors, requestContext } from "../../libraries";
import { Venue } from "../../models/Venue";
/**
* This function enables to create a venue in an organization.
* @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. Whether the user is admin or superadmin or not
* 4. If the venue exists
* @returns Deleted venue
*/

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

// Checks whether currentUser with _id == context.userId exists.
if (currentUser === null) {
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}

const venue = await Venue.findOne({
_id: args.id,
});

if (!venue) {
throw new errors.NotFoundError(
requestContext.translate(VENUE_NOT_FOUND_ERROR.MESSAGE),
VENUE_NOT_FOUND_ERROR.CODE,
VENUE_NOT_FOUND_ERROR.PARAM,
);
}

const organization = await Organization.findById({
_id: venue.organization,
});

// Checks whether organization exists.
if (!organization) {
throw new errors.NotFoundError(
requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE),
ORGANIZATION_NOT_FOUND_ERROR.CODE,
ORGANIZATION_NOT_FOUND_ERROR.PARAM,
);
}

// Checks Whether the user is admin or superadmin or not
if (
!(
organization.admins?.some((admin) => admin._id.equals(context.userId)) ||
currentUser.userType == "SUPERADMIN"
)
) {
throw new errors.UnauthorizedError(
requestContext.translate(ORGANIZATION_NOT_AUTHORIZED_ERROR.MESSAGE),
ORGANIZATION_NOT_AUTHORIZED_ERROR.CODE,
ORGANIZATION_NOT_AUTHORIZED_ERROR.PARAM,
);
}

await Venue.findByIdAndDelete(venue._id);

return {
...venue.toObject(),
organization: organization.toObject(),
};
};
Loading
Loading