Skip to content
6 changes: 4 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ jobs:
- name: Run ESLint to check for linting errors in modified files
env:
CHANGED_FILES: ${{ steps.changed_files.outputs.all_changed_files }}
run: npx eslint ${CHANGED_FILES}
run: npx eslint ${CHANGED_FILES}

- name: Check for formatting errors
run: npm run format:check
env:
CHANGED_FILES: ${{ steps.changed_files.outputs.all_changed_files }}
run: npm run format:check ${CHANGED_FILES}

- name: Run Typescript Type-Checker
run: npm run typecheck
Expand Down
2 changes: 2 additions & 0 deletions codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const config: CodegenConfig = {

EventAttendee: "../models/EventAttendee#InterfaceEventAttendee",

UserFamily: "../models/userFamily#InterfaceUserFamily",

Feedback: "../models/Feedback#InterfaceFeedback",

// File: '../models/File#InterfaceFile',
Expand Down
17 changes: 17 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ type Mutation {
addUserCustomData(dataName: String!, dataValue: Any!, organizationId: ID!): UserCustomData!
addUserImage(file: String!): User!
addUserToGroupChat(chatId: ID!, userId: ID!): GroupChat!
addUserToUserFamily(familyId: ID!, userId: ID!): UserFamily!
adminRemoveEvent(eventId: ID!): Event!
adminRemoveGroup(groupId: ID!): GroupChat!
assignUserTag(input: ToggleUserTagAssignInput!): User
Expand All @@ -543,6 +544,7 @@ type Mutation {
createPlugin(pluginCreatedBy: String!, pluginDesc: String!, pluginName: String!, uninstalledOrgs: [ID!]): Plugin!
createPost(data: PostInput!, file: String): Post
createSampleOrganization: Boolean!
createUserFamily(data: createUserFamilyInput!): UserFamily!
createUserTag(input: CreateUserTagInput!): UserTag
deleteAdvertisementById(id: ID!): DeletePayload!
deleteDonationById(id: ID!): DeletePayload!
Expand Down Expand Up @@ -574,7 +576,9 @@ type Mutation {
removePost(id: ID!): Post
removeSampleOrganization: Boolean!
removeUserCustomData(organizationId: ID!): UserCustomData!
removeUserFamily(familyId: ID!): UserFamily!
removeUserFromGroupChat(chatId: ID!, userId: ID!): GroupChat!
removeUserFromUserFamily(familyId: ID!, userId: ID!): UserFamily!
removeUserImage: User!
removeUserTag(id: ID!): UserTag
revokeRefreshTokenForUser: Boolean!
Expand Down Expand Up @@ -1073,6 +1077,14 @@ type UserEdge {
node: User!
}

type UserFamily {
_id: ID!
admins: [User!]!
creator: User!
title: String
users: [User!]!
}

input UserInput {
appLanguageCode: String
email: EmailAddress!
Expand Down Expand Up @@ -1204,4 +1216,9 @@ input createGroupChatInput {
organizationId: ID!
title: String!
userIds: [ID!]!
}

input createUserFamilyInput {
title: String!
userIds: [ID!]!
}
12 changes: 12 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,24 @@ export const LENGTH_VALIDATION_ERROR = {
PARAM: "stringValidation",
};

export const USER_FAMILY_MIN_MEMBERS_ERROR_CODE = {
MESSAGE: undefined,
CODE: "membersInUserFamilyLessThanOne",
PARAM: "membersInUserFamilyLessThanOne",
};

export const REGEX_VALIDATION_ERROR = {
MESSAGE: "Error: Entered value must be a valid string",
CODE: "string.notValid",
PARAM: "stringValidation",
};

export const USER_FAMILY_NOT_FOUND_ERROR = {
MESSAGE: "Error: User Family Not Found",
CODE: "userfamilyNotFound",
PARAM: "userfamilyNotFound",
};

export const USER_NOT_AUTHORIZED_SUPERADMIN = {
MESSAGE: "Error: Current user must be a SUPERADMIN",
CODE: "role.notValid.superadmin",
Expand Down
5 changes: 2 additions & 3 deletions src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type { InterfaceEvent } from "./Event";
import type { InterfaceMembershipRequest } from "./MembershipRequest";
import type { InterfaceOrganization } from "./Organization";
import { createLoggingMiddleware } from "../libraries/dbLogger";
import { LOG } from "../constants";

/**
* This is an interface that represents a database(MongoDB) document for User.
Expand Down Expand Up @@ -278,7 +277,7 @@ const userSchema = new Schema(
},
{
timestamps: true,
}
},
);

userSchema.plugin(mongoosePaginate);
Expand All @@ -291,4 +290,4 @@ createLoggingMiddleware(userSchema, "User");
// This syntax is needed to prevent Mongoose OverwriteModelError while running tests.
export const User = (models.User || userModel()) as ReturnType<
typeof userModel
>;
>;
56 changes: 56 additions & 0 deletions src/models/userFamily.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { PopulatedDoc, Types, Document, Model } from "mongoose";
import { Schema, model, models } from "mongoose";
import type { InterfaceUser } from "./User";
/**
* This is an interface that represents a database(MongoDB) document for Family.
*/

export interface InterfaceUserFamily {
_id: Types.ObjectId;
title: string;
users: PopulatedDoc<InterfaceUser & Document>[];
admins: PopulatedDoc<InterfaceUser & Document>[];
creator: PopulatedDoc<InterfaceUser & Document>[];
}

/**
* @param title - Name of the user Family (type: String)
* Description: Name of the user Family.
*/

/**
* @param users - Members associated with the user Family (type: String)
* Description: Members associated with the user Family.
*/
const userFamilySchema = new Schema({
title: {
type: String,
required: true,
},
users: [
{
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
],
admins: [
{
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
],
creator: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
});

const userFamilyModel = (): Model<InterfaceUserFamily> =>
model<InterfaceUserFamily>("UserFamily", userFamilySchema);

// This syntax is needed to prevent Mongoose OverwriteModelError while running tests.
export const UserFamily = (models.UserFamily ||
userFamilyModel()) as ReturnType<typeof userFamilyModel>;
82 changes: 82 additions & 0 deletions src/resolvers/Mutation/addUserToUserFamily.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import "dotenv/config";
import type { MutationResolvers } from "../../types/generatedGraphQLTypes";
import { errors, requestContext } from "../../libraries";
import { adminCheck } from "../../utilities/userFamilyAdminCheck";
import { User } from "../../models";
import { UserFamily } from "../../models/userFamily";
import {
USER_FAMILY_NOT_FOUND_ERROR,
USER_ALREADY_MEMBER_ERROR,
USER_NOT_FOUND_ERROR,
} from "../../constants";
/**
* This function adds user to the family.
* @param _parent - parent of current request
* @param args - payload provided with the request
* @param context - context of the entire application
* @remarks The following checks are done:
* 1. If the family exists
* 2. If the user exists
* 3. If the user is already member of the family
* 4. If the user is admin of the user Family
* @returns Updated family
*/
export const addUserToUserFamily: MutationResolvers["addUserToUserFamily"] =
async (_parent, args, context) => {
const userFamily = await UserFamily.findOne({
_id: args.familyId,
}).lean();

const currentUser = await User.findById({
_id: context.userId,
});

// Checks whether user with _id === args.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,
);
}

//check wheather family exists
if (!userFamily) {
throw new errors.NotFoundError(
requestContext.translate(USER_FAMILY_NOT_FOUND_ERROR.MESSAGE),
USER_FAMILY_NOT_FOUND_ERROR.CODE,
USER_FAMILY_NOT_FOUND_ERROR.PARAM,
);
}

//check whether user is admin of the family
await adminCheck(currentUser?._id, userFamily);

const isUserMemberOfUserFamily = userFamily.users.some((user) => {
user.equals(args.userId);
});

// Checks whether user with _id === args.userId is already a member of Family.
if (isUserMemberOfUserFamily) {
throw new errors.ConflictError(
requestContext.translate(USER_ALREADY_MEMBER_ERROR.MESSAGE),
USER_ALREADY_MEMBER_ERROR.CODE,
USER_ALREADY_MEMBER_ERROR.PARAM,
);
}

// Adds args.userId to users lists on family group and return the updated family.
return await UserFamily.findOneAndUpdate(
{
_id: args.familyId,
},
{
$push: {
users: args.userId,
},
},
{
new: true,
},
).lean();
};
81 changes: 81 additions & 0 deletions src/resolvers/Mutation/createUserFamily.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { MutationResolvers } from "../../types/generatedGraphQLTypes";
import { User } from "../../models";
import { errors, requestContext } from "../../libraries";
import {
LENGTH_VALIDATION_ERROR,
USER_FAMILY_MIN_MEMBERS_ERROR_CODE,
USER_NOT_FOUND_ERROR,
} from "../../constants";
import { isValidString } from "../../libraries/validators/validateString";
import { UserFamily } from "../../models/userFamily";
import { superAdminCheck } from "../../utilities";
/**
* This Function enables to create a user Family
* @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 user is super admin
* 3. If there are atleast two members in the family.
* @returns Created user Family
*/
export const createUserFamily: MutationResolvers["createUserFamily"] = async (
_parent,
args,
context,
) => {
const currentUser = await User.findById({
_id: context.userId,
});

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

// Check whether the user is super admin.
superAdminCheck(currentUser);

let validationResultName = {
isLessThanMaxLength: false,
};

if (args && args.data && typeof args.data.title === "string") {
validationResultName = isValidString(args.data.title, 256);
}

if (!validationResultName.isLessThanMaxLength) {
throw new errors.InputValidationError(
requestContext.translate(
`${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in name`,
),
LENGTH_VALIDATION_ERROR.CODE,
);
}

// Check if there are at least 2 members
if (args.data?.userIds.length < 2) {
throw new errors.InputValidationError(
requestContext.translate(USER_FAMILY_MIN_MEMBERS_ERROR_CODE.MESSAGE),
USER_FAMILY_MIN_MEMBERS_ERROR_CODE.CODE,
USER_FAMILY_MIN_MEMBERS_ERROR_CODE.PARAM,
);
}

const userfamilyTitle = args.data?.title;

const createdUserFamily = await UserFamily.create({
...args.data,
title: userfamilyTitle,
users: [context.userId, ...args.data.userIds],
admins: [context.userId],
creator: context.userId,
});

return createdUserFamily.toObject();
};
8 changes: 8 additions & 0 deletions src/resolvers/Mutation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ import { removeComment } from "./removeComment";
import { removeDirectChat } from "./removeDirectChat";
import { removeEvent } from "./removeEvent";
import { removeEventAttendee } from "./removeEventAttendee";
import { addUserToUserFamily } from "./adminAddMemberToUserFamily";
import { removeUserFromUserFamily } from "./adminRemoveMemberFromUserFamily";
import { removeUserFamily } from "./removeUserFamily";
import { createUserFamily } from "./createUserFamily";
import { removeGroupChat } from "./removeGroupChat";
import { removeAdvertisement } from "./removeAdvertisement";
import { removeMember } from "./removeMember";
Expand Down Expand Up @@ -104,6 +108,10 @@ export const Mutation: MutationResolvers = {
addUserToGroupChat,
adminRemoveEvent,
adminRemoveGroup,
addUserToUserFamily,
removeUserFamily,
removeUserFromUserFamily,
createUserFamily,
assignUserTag,
blockPluginCreationBySuperadmin,
blockUser,
Expand Down
Loading