Skip to content

Commit

Permalink
more tag actions
Browse files Browse the repository at this point in the history
  • Loading branch information
meetulr committed Sep 21, 2024
1 parent f7c8210 commit 07f4b9c
Show file tree
Hide file tree
Showing 6 changed files with 369 additions and 0 deletions.
169 changes: 169 additions & 0 deletions src/resolvers/Mutation/addToUserTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { Types } from "mongoose";
import {
TAG_NOT_FOUND,
USER_NOT_FOUND_ERROR,
USER_NOT_AUTHORIZED_ERROR,
} from "../../constants";
import { errors, requestContext } from "../../libraries";
import type {
InterfaceAppUserProfile,
InterfaceOrganizationTagUser,
InterfaceUser,
} from "../../models";
import {
AppUserProfile,
OrganizationTagUser,
TagUser,
User,
} from "../../models";
import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile";
import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache";
import { cacheUsers } from "../../services/UserCache/cacheUser";
import { findUserInCache } from "../../services/UserCache/findUserInCache";
import type { MutationResolvers } from "../../types/generatedGraphQLTypes";

/**
* This function enables an admin to assign multiple tags and their ancestors to users with a specified tag.
* @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 current user exists and has a profile.
* 2. If the current user is an admin for the organization of the tags.
* 3. If the currentTagId exists and the selected tags exist.
* 4. Assign the tags to users who have the currentTagId.
* @returns Array of tags that were assigned to users.
*/
export const addToUserTags: MutationResolvers["addToUserTags"] = async (
_parent,
args,
context,
) => {
let currentUser: InterfaceUser | null;
const userFoundInCache = await findUserInCache([context.userId]);
currentUser = userFoundInCache[0];
if (currentUser === null) {
currentUser = await User.findOne({
_id: context.userId,
}).lean();
if (currentUser !== null) {
await cacheUsers([currentUser]);
}
}

// Checks whether the currentUser exists.
if (!currentUser) {
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}
let currentUserAppProfile: InterfaceAppUserProfile | null;
const appUserProfileFoundInCache = await findAppUserProfileCache([
currentUser.appUserProfileId.toString(),
]);
currentUserAppProfile = appUserProfileFoundInCache[0];
if (currentUserAppProfile === null) {
currentUserAppProfile = await AppUserProfile.findOne({
userId: currentUser._id,
}).lean();
if (currentUserAppProfile !== null) {
await cacheAppUserProfile([currentUserAppProfile]);
}
}
if (!currentUserAppProfile) {
throw new errors.UnauthorizedError(
requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE),
USER_NOT_AUTHORIZED_ERROR.CODE,
USER_NOT_AUTHORIZED_ERROR.PARAM,
);
}

// Get the current tag object
const currentTag = await OrganizationTagUser.findOne({
_id: args.input.currentTagId,
}).lean();

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

// Boolean to determine whether user is an admin of the organization of the current tag.
const currentUserIsOrganizationAdmin = currentUserAppProfile.adminFor.some(
(orgId) =>
orgId &&
new Types.ObjectId(orgId.toString()).equals(currentTag.organizationId),
);
if (!(currentUserIsOrganizationAdmin || currentUserAppProfile.isSuperAdmin)) {
throw new errors.UnauthorizedError(
requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE),
USER_NOT_AUTHORIZED_ERROR.CODE,
USER_NOT_AUTHORIZED_ERROR.PARAM,
);
}

// Find all users with the currentTagId
const usersWithCurrentTag = await TagUser.find({
tagId: currentTag._id,
}).lean();

const userIdsWithCurrentTag = usersWithCurrentTag.map(
(userTag) => userTag.userId,
);

// Validate selected tags
const selectedTags = await OrganizationTagUser.find({
_id: { $in: args.input.selectedTagIds },
}).lean();

const selectedTagMap = new Map(
selectedTags.map((tag) => [tag._id.toString(), tag]),
);

if (selectedTags.length === 0) {
throw new errors.NotFoundError(
requestContext.translate(TAG_NOT_FOUND.MESSAGE),
TAG_NOT_FOUND.CODE,
TAG_NOT_FOUND.PARAM,
);
}

// Find and assign ancestor tags
const allTagsToAssign = new Set<string>();
for (const tag of selectedTags) {
let currentTagToProcess: InterfaceOrganizationTagUser | null = tag;
while (currentTagToProcess) {
allTagsToAssign.add(currentTagToProcess._id.toString());
if (currentTagToProcess.parentTagId) {
const parentTag: any = await OrganizationTagUser.findOne({
_id: currentTagToProcess.parentTagId,
}).lean();
currentTagToProcess = parentTag || null;
} else {
currentTagToProcess = null;
}
}
}

const tagUserDocs = userIdsWithCurrentTag.flatMap((userId) =>
Array.from(allTagsToAssign).map((tagId) => ({
updateOne: {
filter: { userId, tagId: new Types.ObjectId(tagId) },
update: { $setOnInsert: { userId, tagId } },
upsert: true,
setDefaultsOnInsert: true,
},
})),
);

if (tagUserDocs.length > 0) {
await TagUser.bulkWrite(tagUserDocs);
}

return currentTag;
};
4 changes: 4 additions & 0 deletions src/resolvers/Mutation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { addUserImage } from "./addUserImage";
import { addUserToGroupChat } from "./addUserToGroupChat";
import { addUserToUserFamily } from "./addUserToUserFamily";
import { addPeopleToUserTag } from "./addPeopleToUserTag";
import { addToUserTags } from "./addToUserTags";
import { adminRemoveGroup } from "./adminRemoveGroup";
import { assignUserTag } from "./assignUserTag";
import { blockPluginCreationBySuperadmin } from "./blockPluginCreationBySuperadmin";
Expand Down Expand Up @@ -87,6 +88,7 @@ import { removeUserFromGroupChat } from "./removeUserFromGroupChat";
import { removeUserFromUserFamily } from "./removeUserFromUserFamily";
import { removeUserImage } from "./removeUserImage";
import { removeUserTag } from "./removeUserTag";
import { removeFromUserTags } from "./removeFromUserTags";
import { resetCommunity } from "./resetCommunity";
import { revokeRefreshTokenForUser } from "./revokeRefreshTokenForUser";
import { saveFcmToken } from "./saveFcmToken";
Expand Down Expand Up @@ -138,6 +140,7 @@ export const Mutation: MutationResolvers = {
adminRemoveGroup,
addUserToUserFamily,
addPeopleToUserTag,
addToUserTags,
removeUserFamily,
removeUserFromUserFamily,
createUserFamily,
Expand Down Expand Up @@ -214,6 +217,7 @@ export const Mutation: MutationResolvers = {
removeUserFromGroupChat,
removeUserImage,
removeUserTag,
removeFromUserTags,
resetCommunity,
revokeRefreshTokenForUser,
saveFcmToken,
Expand Down
166 changes: 166 additions & 0 deletions src/resolvers/Mutation/removeFromUserTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { Types } from "mongoose";
import {
TAG_NOT_FOUND,
USER_NOT_FOUND_ERROR,
USER_NOT_AUTHORIZED_ERROR,
} from "../../constants";
import { errors, requestContext } from "../../libraries";
import type {
InterfaceAppUserProfile,
InterfaceOrganizationTagUser,
InterfaceUser,
} from "../../models";
import {
AppUserProfile,
OrganizationTagUser,
TagUser,
User,
} from "../../models";
import { cacheAppUserProfile } from "../../services/AppUserProfileCache/cacheAppUserProfile";
import { findAppUserProfileCache } from "../../services/AppUserProfileCache/findAppUserProfileCache";
import { cacheUsers } from "../../services/UserCache/cacheUser";
import { findUserInCache } from "../../services/UserCache/findUserInCache";
import type { MutationResolvers } from "../../types/generatedGraphQLTypes";

/**
* This function enables an admin to remove multiple tags and their ancestors from users with a specified tag.
* @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 current user exists and has a profile.
* 2. If the current user is an admin for the organization of the tags.
* 3. If the currentTagId exists and the selected tags exist.
* 4. Remove the tags from users who have the currentTagId.
* @returns Array of tags that were removed from users.
*/
export const removeFromUserTags: MutationResolvers["removeFromUserTags"] =
async (_parent, args, context) => {
let currentUser: InterfaceUser | null;
const userFoundInCache = await findUserInCache([context.userId]);
currentUser = userFoundInCache[0];
if (currentUser === null) {
currentUser = await User.findOne({
_id: context.userId,
}).lean();
if (currentUser !== null) {
await cacheUsers([currentUser]);
}
}

// Checks whether the currentUser exists.
if (!currentUser) {
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
USER_NOT_FOUND_ERROR.CODE,
USER_NOT_FOUND_ERROR.PARAM,
);
}
let currentUserAppProfile: InterfaceAppUserProfile | null;
const appUserProfileFoundInCache = await findAppUserProfileCache([
currentUser.appUserProfileId.toString(),
]);
currentUserAppProfile = appUserProfileFoundInCache[0];
if (currentUserAppProfile === null) {
currentUserAppProfile = await AppUserProfile.findOne({
userId: currentUser._id,
}).lean();
if (currentUserAppProfile !== null) {
await cacheAppUserProfile([currentUserAppProfile]);
}
}
if (!currentUserAppProfile) {
throw new errors.UnauthorizedError(
requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE),
USER_NOT_AUTHORIZED_ERROR.CODE,
USER_NOT_AUTHORIZED_ERROR.PARAM,
);
}

// Get the current tag object
const currentTag = await OrganizationTagUser.findOne({
_id: args.input.currentTagId,
}).lean();

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

// Boolean to determine whether user is an admin of the organization of the current tag.
const currentUserIsOrganizationAdmin = currentUserAppProfile.adminFor.some(
(orgId) =>
orgId &&
new Types.ObjectId(orgId.toString()).equals(currentTag.organizationId),
);
if (
!(currentUserIsOrganizationAdmin || currentUserAppProfile.isSuperAdmin)
) {
throw new errors.UnauthorizedError(
requestContext.translate(USER_NOT_AUTHORIZED_ERROR.MESSAGE),
USER_NOT_AUTHORIZED_ERROR.CODE,
USER_NOT_AUTHORIZED_ERROR.PARAM,
);
}

// Find all users with the currentTagId
const usersWithCurrentTag = await TagUser.find({
tagId: currentTag._id,
}).lean();

const userIdsWithCurrentTag = usersWithCurrentTag.map(
(userTag) => userTag.userId,
);

// Validate selected tags
const selectedTags = await OrganizationTagUser.find({
_id: { $in: args.input.selectedTagIds },
}).lean();

console.log("here");
const selectedTagMap = new Map(
selectedTags.map((tag) => [tag._id.toString(), tag]),
);

if (selectedTags.length === 0) {
throw new errors.NotFoundError(
requestContext.translate(TAG_NOT_FOUND.MESSAGE),
TAG_NOT_FOUND.CODE,
TAG_NOT_FOUND.PARAM,
);
}

// Find and remove ancestor tags
const allTagsToRemove = new Set<string>();
for (const tag of selectedTags) {
let currentTagToProcess: InterfaceOrganizationTagUser | null = tag;
while (currentTagToProcess) {
allTagsToRemove.add(currentTagToProcess._id.toString());
if (currentTagToProcess.parentTagId) {
const parentTag: any = await OrganizationTagUser.findOne({
_id: currentTagToProcess.parentTagId,
}).lean();
currentTagToProcess = parentTag || null;
} else {
currentTagToProcess = null;
}
}
}

const tagUserDocs = userIdsWithCurrentTag.flatMap((userId) =>
Array.from(allTagsToRemove).map((tagId) => ({
deleteOne: {
filter: { userId, tagId: new Types.ObjectId(tagId) },
},
})),
);

if (tagUserDocs.length > 0) {
await TagUser.bulkWrite(tagUserDocs);
}

return currentTag;
};
5 changes: 5 additions & 0 deletions src/typeDefs/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,11 @@ export const inputs = gql`
tagId: ID!
}
input TagActionsInput {
currentTagId: ID!
selectedTagIds: [ID!]!
}
input ToggleUserTagAssignInput {
userId: ID!
tagId: ID!
Expand Down
4 changes: 4 additions & 0 deletions src/typeDefs/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export const mutations = gql`
addPeopleToUserTag(input: AddPeopleToUserTagInput!): UserTag @auth
addToUserTags(input: TagActionsInput!): UserTag @auth
removeUserFromUserFamily(userId: ID!, familyId: ID!): UserFamily! @auth
removeUserFamily(familyId: ID!): UserFamily! @auth
Expand Down Expand Up @@ -235,6 +237,8 @@ export const mutations = gql`
removeUserTag(id: ID!): UserTag @auth
removeFromUserTags(input: TagActionsInput!): UserTag @auth
removeSampleOrganization: Boolean! @auth
removeUserFromGroupChat(userId: ID!, chatId: ID!): GroupChat! @auth
Expand Down
Loading

0 comments on commit 07f4b9c

Please sign in to comment.