feat: feedback changes added

This commit is contained in:
mirarifhasan
2024-02-12 19:54:39 +06:00
committed by Andrew Bastin
parent a0d40c8776
commit 3c2b48a635
7 changed files with 52 additions and 65 deletions

View File

@@ -27,7 +27,7 @@ import {
} from './input-types.args';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
import { UserDeleteData } from 'src/user/user.model';
import { UserDeletionResult } from 'src/user/user.model';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => Admin)
@@ -71,20 +71,20 @@ export class AdminResolver {
}
@Mutation(() => Boolean, {
description: 'Revoke a user invite by invitee email',
description: 'Revoke a user invites by invitee emails',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async revokeUserInvitationByAdmin(
async revokeUserInvitationsByAdmin(
@GqlAdmin() adminUser: Admin,
@Args({
name: 'inviteeEmail',
description: 'Invite Email',
type: () => ID,
name: 'inviteeEmails',
description: 'Invitee Emails',
type: () => [String],
})
inviteeEmail: string,
inviteeEmails: string[],
): Promise<boolean> {
const invite = await this.adminService.revokeUserInvite(
inviteeEmail,
const invite = await this.adminService.revokeUserInvites(
inviteeEmails,
adminUser.uid,
);
if (E.isLeft(invite)) throwErr(invite.left);
@@ -93,6 +93,7 @@ export class AdminResolver {
@Mutation(() => Boolean, {
description: 'Delete an user account from infra',
deprecationReason: 'Use removeUsersByAdmin instead',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async removeUserByAdmin(
@@ -108,7 +109,7 @@ export class AdminResolver {
return removedUser.right;
}
@Mutation(() => [UserDeleteData], {
@Mutation(() => [UserDeletionResult], {
description: 'Delete user accounts from infra',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
@@ -119,7 +120,7 @@ export class AdminResolver {
type: () => [ID],
})
userUIDs: string[],
): Promise<UserDeleteData[]> {
): Promise<UserDeletionResult[]> {
const deletionResults = await this.adminService.removeUserAccounts(
userUIDs,
);
@@ -129,6 +130,7 @@ export class AdminResolver {
@Mutation(() => Boolean, {
description: 'Make user an admin',
deprecationReason: 'Use makeUsersAdmin instead',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async makeUserAdmin(
@@ -188,6 +190,7 @@ export class AdminResolver {
@Mutation(() => Boolean, {
description: 'Remove user as admin',
deprecationReason: 'Use demoteUsersByAdmin instead',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async removeUserAsAdmin(
@@ -207,7 +210,7 @@ export class AdminResolver {
description: 'Remove users as admin',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async removeUsersAsAdmin(
async demoteUsersByAdmin(
@Args({
name: 'userUIDs',
description: 'users UID',
@@ -215,7 +218,7 @@ export class AdminResolver {
})
userUIDs: string[],
): Promise<boolean> {
const isUpdated = await this.adminService.removeUsersAsAdmin(userUIDs);
const isUpdated = await this.adminService.demoteUsersByAdmin(userUIDs);
if (E.isLeft(isUpdated)) throwErr(isUpdated.left);
return isUpdated.right;
}
@@ -372,14 +375,4 @@ export class AdminResolver {
userInvited(@GqlUser() admin: AuthUser) {
return this.pubsub.asyncIterator(`admin/${admin.uid}/invited`);
}
@Subscription(() => InvitedUser, {
description: 'Listen for User Invite Revocation',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlAdminGuard)
userRevoked(@GqlUser() admin: AuthUser) {
return this.pubsub.asyncIterator(`admin/${admin.uid}/invitation_revoked`);
}
}

View File

@@ -198,7 +198,7 @@ describe('AdminService', () => {
mockUserService.removeUsersAsAdmin.mockResolvedValueOnce(E.right(true));
return expect(
await adminService.removeUsersAsAdmin(['123456']),
await adminService.demoteUsersByAdmin(['123456']),
).toEqualRight(true);
});
@@ -208,7 +208,7 @@ describe('AdminService', () => {
]);
mockUserService.removeUsersAsAdmin.mockResolvedValueOnce(E.right(true));
return expect(await adminService.removeUsersAsAdmin(['123'])).toEqualLeft(
return expect(await adminService.demoteUsersByAdmin(['123'])).toEqualLeft(
ONLY_ONE_ADMIN_ACCOUNT,
);
});

View File

@@ -13,6 +13,7 @@ import {
ONLY_ONE_ADMIN_ACCOUNT,
TEAM_INVITE_ALREADY_MEMBER,
TEAM_INVITE_NO_INVITE_FOUND,
USERS_NOT_FOUND,
USER_ALREADY_INVITED,
USER_INVITATION_NOT_FOUND,
USER_IS_ADMIN,
@@ -29,7 +30,7 @@ import { TeamMemberRole } from '../team/team.model';
import { ShortcodeService } from 'src/shortcode/shortcode.service';
import { ConfigService } from '@nestjs/config';
import { OffsetPaginationArgs } from 'src/types/input-types.args';
import { UserDeleteData } from 'src/user/user.model';
import { UserDeletionResult } from 'src/user/user.model';
@Injectable()
export class AdminService {
@@ -139,35 +140,28 @@ export class AdminService {
* @returns an Either of boolean or error
*/
async updateUserDisplayName(userUid: string, displayName: string) {
const updatedUser = await this.userService.updateUser(userUid, displayName);
const updatedUser = await this.userService.updateUserDisplayName(
userUid,
displayName,
);
if (E.isLeft(updatedUser)) return E.left(updatedUser.left);
return E.right(true);
}
/**
* Revoke infra level user invitation
* @param inviteeEmail Invitee's email
* Revoke infra level user invitations
* @param inviteeEmails Invitee's emails
* @param adminUid Admin Uid
* @returns an Either of array of `InvitedUser` object or error string
* @returns an Either of boolean or error string
*/
async revokeUserInvite(inviteeEmail: string, adminUid: string) {
async revokeUserInvites(inviteeEmails: string[], adminUid: string) {
try {
const deletedInvitee = await this.prisma.invitedUsers.delete({
await this.prisma.invitedUsers.deleteMany({
where: {
inviteeEmail,
inviteeEmail: { in: inviteeEmails },
},
});
const invitedUser = <InvitedUser>{
adminEmail: deletedInvitee.adminEmail,
adminUid: deletedInvitee.adminUid,
inviteeEmail: deletedInvitee.inviteeEmail,
invitedOn: deletedInvitee.invitedOn,
};
this.pubsub.publish(`admin/${adminUid}/invitation_revoked`, invitedUser);
return E.right(true);
} catch (error) {
return E.left(USER_INVITATION_NOT_FOUND);
@@ -434,44 +428,44 @@ export class AdminService {
/**
* Remove user (not Admin) accounts by UIDs
* @param userUid User UIDs
* @param userUIDs User UIDs
* @returns an Either of boolean or error
*/
async removeUserAccounts(userUIDs: string[]) {
const allUsers = await this.userService.findUsersByIds(userUIDs);
if (allUsers.length === 0) return E.left(USER_NOT_FOUND);
const allUsersList = await this.userService.findUsersByIds(userUIDs);
if (allUsersList.length === 0) return E.left(USERS_NOT_FOUND);
const userDeleteResult: UserDeleteData[] = [];
const userDeleteResult: UserDeletionResult[] = [];
// Admin user can not be deleted without removing admin status/role
allUsers.forEach((user) => {
allUsersList.forEach((user) => {
if (user.isAdmin) {
userDeleteResult.push({
userUID: user.uid,
success: false,
isDeleted: false,
errorMessage: ADMIN_CAN_NOT_BE_DELETED,
});
}
});
const normalUsers = allUsers.filter((user) => !user.isAdmin);
const nonAdminUsers = allUsersList.filter((user) => !user.isAdmin);
const deletionPromises = normalUsers.map((user) => {
const deletionPromises = nonAdminUsers.map((user) => {
return this.userService
.deleteUserByUID(user)()
.then((res) => {
if (E.isLeft(res)) {
return {
userUID: user.uid,
success: false,
isDeleted: false,
errorMessage: res.left,
} as UserDeleteData;
} as UserDeletionResult;
}
return {
userUID: user.uid,
success: true,
isDeleted: true,
errorMessage: null,
} as UserDeleteData;
} as UserDeletionResult;
});
});
const promiseResult = await Promise.allSettled(deletionPromises);
@@ -526,7 +520,7 @@ export class AdminService {
* @param userUIDs User UIDs
* @returns an Either of boolean or error
*/
async removeUsersAsAdmin(userUIDs: string[]) {
async demoteUsersByAdmin(userUIDs: string[]) {
const adminUsers = await this.userService.fetchAdminUsers();
const remainingAdmins = adminUsers.filter(

View File

@@ -31,7 +31,7 @@ import { Shortcode } from 'src/shortcode/shortcode.model';
// A custom message type that defines the topic and the corresponding payload.
// For every module that publishes a subscription add its type def and the possible subscription type.
export type TopicDef = {
[topic: `admin/${string}/${'invited' | 'invitation_revoked'}`]: InvitedUser;
[topic: `admin/${string}/${'invited'}`]: InvitedUser;
[topic: `user/${string}/${'updated' | 'deleted'}`]: User;
[topic: `user_settings/${string}/${'created' | 'updated'}`]: UserSettings;
[

View File

@@ -58,7 +58,7 @@ registerEnumType(SessionType, {
});
@ObjectType()
export class UserDeleteData {
export class UserDeletionResult {
@Field(() => ID, {
description: 'UID of the user',
})
@@ -67,7 +67,7 @@ export class UserDeleteData {
@Field(() => Boolean, {
description: 'Flag to determine if user deletion was successful or not',
})
success: Boolean;
isDeleted: Boolean;
@Field({
nullable: true,

View File

@@ -438,7 +438,7 @@ describe('UserService', () => {
test('should resolve right and update user', async () => {
mockPrisma.user.update.mockResolvedValueOnce(user);
const result = await userService.updateUser(user.uid, user.displayName);
const result = await userService.updateUserDisplayName(user.uid, user.displayName);
expect(result).toEqualRight({
...user,
currentGQLSession: JSON.stringify(user.currentGQLSession),
@@ -448,7 +448,7 @@ describe('UserService', () => {
test('should resolve right and publish user updated subscription', async () => {
mockPrisma.user.update.mockResolvedValueOnce(user);
await userService.updateUser(user.uid, user.displayName);
await userService.updateUserDisplayName(user.uid, user.displayName);
expect(mockPubSub.publish).toHaveBeenCalledWith(
`user/${user.uid}/updated`,
{
@@ -461,7 +461,7 @@ describe('UserService', () => {
test('should resolve left and error when invalid user uid is passed', async () => {
mockPrisma.user.update.mockRejectedValueOnce('NotFoundError');
const result = await userService.updateUser(
const result = await userService.updateUserDisplayName(
'invalidUserUid',
user.displayName,
);

View File

@@ -90,9 +90,9 @@ export class UserService {
}
/**
* Find Non-Admin Users with given IDs
* Find users with given IDs
* @param userUIDs User IDs
* @returns Option of found Users
* @returns Array of found Users
*/
async findUsersByIds(userUIDs: string[]): Promise<AuthUser[]> {
const users = await this.prisma.user.findMany({
@@ -290,7 +290,7 @@ export class UserService {
* @param displayName User's displayName
* @returns a Either of User or error
*/
async updateUser(userUID: string, displayName: string) {
async updateUserDisplayName(userUID: string, displayName: string) {
try {
const dbUpdatedUser = await this.prisma.user.update({
where: { uid: userUID },