From 8172ebc802399b9a328b29f11633d3c5d7e4b5b0 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Fri, 25 Nov 2022 10:15:38 +0000 Subject: [PATCH 01/16] feat: added endpoint to get users with corresponding teams --- .../modules/teams/interfaces/users-with-teams.interface.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 backend/src/modules/teams/interfaces/users-with-teams.interface.ts diff --git a/backend/src/modules/teams/interfaces/users-with-teams.interface.ts b/backend/src/modules/teams/interfaces/users-with-teams.interface.ts new file mode 100644 index 000000000..eceba46b8 --- /dev/null +++ b/backend/src/modules/teams/interfaces/users-with-teams.interface.ts @@ -0,0 +1,6 @@ +import { ObjectId } from 'mongoose'; + +export interface UsersWithTeams { + _id: ObjectId; + teamsNames: Array>; +} From 38b76f29c672e3d75809d6ffae88fd7a195595a1 Mon Sep 17 00:00:00 2001 From: GuiSanto Date: Wed, 30 Nov 2022 11:35:11 +0000 Subject: [PATCH 02/16] fix: file no longer needed --- .../modules/teams/interfaces/users-with-teams.interface.ts | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 backend/src/modules/teams/interfaces/users-with-teams.interface.ts diff --git a/backend/src/modules/teams/interfaces/users-with-teams.interface.ts b/backend/src/modules/teams/interfaces/users-with-teams.interface.ts deleted file mode 100644 index eceba46b8..000000000 --- a/backend/src/modules/teams/interfaces/users-with-teams.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ObjectId } from 'mongoose'; - -export interface UsersWithTeams { - _id: ObjectId; - teamsNames: Array>; -} From 6d2cedf67d682259a60616575c293c823d76f60f Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Wed, 30 Nov 2022 17:52:58 +0000 Subject: [PATCH 03/16] feat: member only sees user teams count and which user is an admin --- .../UsersList/partials/CardUser/CardBody.tsx | 5 +- .../partials/CardUser/SuperAdmin.tsx | 77 ++++++++++++++----- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx index 74ae6b90c..7307592ee 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx @@ -8,6 +8,7 @@ import Box from '@/components/Primitives/Box'; import Flex from '@/components/Primitives/Flex'; import Text from '@/components/Primitives/Text'; import { UserWithTeams } from '@/types/user/user'; +// eslint-disable-next-line import/no-named-as-default import SuperAdmin from './SuperAdmin'; import CardEnd from './CardEnd'; import CardTitle from './CardTitle'; @@ -78,7 +79,9 @@ const CardBody = React.memo(({ userWithTeams }) => { - {loggedUserIsSAdmin && } + {loggedUserIsSAdmin && ( + + )} {loggedUserIsSAdmin && } diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx index 054a287a0..a453eb505 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx @@ -4,37 +4,76 @@ import { Switch, SwitchThumb } from '@/components/Primitives/Switch'; import Icon from '@/components/icons/Icon'; import { useState } from 'react'; +import Separator from '@/components/Primitives/Separator'; + type SuperAdminProps = { userSAdmin: boolean; + loggedUserSAdmin: boolean | undefined; }; -const SuperAdmin = ({ userSAdmin }: SuperAdminProps) => { +const SuperAdmin = ({ userSAdmin, loggedUserSAdmin }: SuperAdminProps) => { const [checkedState, setCheckedState] = useState(userSAdmin); const handleSuperAdminChange = () => { setCheckedState(!checkedState); }; + + if (loggedUserSAdmin) { + return ( + + + {checkedState && ( + + + + )} + {!checkedState && } + + + Super Admin + + + + ); + } return ( - - {checkedState && ( - - - - )} - {!checkedState && } - - - Super Admin - + {userSAdmin && ( + + SUPER ADMIN + + )} +>>>>>>> bd6d377 (feat: member only sees user teams count and which user is an admin) ); }; From 43a97018c0b88670c4016623c3fe659f3bb1e7e5 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Fri, 2 Dec 2022 16:10:30 +0000 Subject: [PATCH 04/16] chore: added userSAdmin endpoint, update users frontend and show teams tooltip when mouse hover --- backend/src/libs/guards/superAdmin.guard.ts | 16 ++++++ .../applications/update.user.application.ts | 5 ++ .../users/controller/users.controller.ts | 56 ++++++++++++++++++- .../src/modules/users/dto/update.user.dto.ts | 30 ++++++++++ .../update.user.service.interface.ts | 7 ++- .../services/update.user.service.interface.ts | 10 +++- .../users/services/update.user.service.ts | 8 +++ .../swagger/update.superadmin.swagger.ts | 9 +++ frontend/src/api/userService.tsx | 5 +- .../UsersList/partials/CardUser/CardBody.tsx | 29 ++++++++-- .../UsersList/partials/CardUser/EditUser.tsx | 21 ++++--- .../partials/CardUser/SuperAdmin.tsx | 51 +++++++++-------- .../UsersList/partials/ListOfCards/index.tsx | 2 +- frontend/src/hooks/useUser.tsx | 33 ++++++++++- frontend/src/hooks/useUserUtils.tsx | 43 ++++++++++++++ frontend/src/pages/users/[userId].tsx | 3 + frontend/src/store/user/atoms/user.atom.tsx | 2 +- frontend/src/types/user/user.ts | 6 ++ 18 files changed, 284 insertions(+), 52 deletions(-) create mode 100644 backend/src/libs/guards/superAdmin.guard.ts create mode 100644 backend/src/modules/users/dto/update.user.dto.ts create mode 100644 backend/src/modules/users/swagger/update.superadmin.swagger.ts create mode 100644 frontend/src/hooks/useUserUtils.tsx create mode 100644 frontend/src/pages/users/[userId].tsx diff --git a/backend/src/libs/guards/superAdmin.guard.ts b/backend/src/libs/guards/superAdmin.guard.ts new file mode 100644 index 000000000..2277566b2 --- /dev/null +++ b/backend/src/libs/guards/superAdmin.guard.ts @@ -0,0 +1,16 @@ +import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common'; + +@Injectable() +export class SuperAdminGuard implements CanActivate { + async canActivate(context: ExecutionContext) { + const request = context.switchToHttp().getRequest(); + + const user = request.user; + + try { + return user.isSAdmin; + } catch (error) { + throw new ForbiddenException(); + } + } +} diff --git a/backend/src/modules/users/applications/update.user.application.ts b/backend/src/modules/users/applications/update.user.application.ts index ba364c432..30304ac9f 100644 --- a/backend/src/modules/users/applications/update.user.application.ts +++ b/backend/src/modules/users/applications/update.user.application.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import UpdateUserDto from '../dto/update.user.dto'; import { UpdateUserApplication } from '../interfaces/applications/update.user.service.interface'; import { UpdateUserService } from '../interfaces/services/update.user.service.interface'; import { TYPES } from '../interfaces/types'; @@ -21,4 +22,8 @@ export class UpdateUserApplicationImpl implements UpdateUserApplication { checkEmail(token: string) { return this.updateUserService.checkEmail(token); } + + updateSuperAdmin(user: UpdateUserDto) { + return this.updateUserService.updateSuperAdmin(user); + } } diff --git a/backend/src/modules/users/controller/users.controller.ts b/backend/src/modules/users/controller/users.controller.ts index 53b78264d..2d17fd565 100644 --- a/backend/src/modules/users/controller/users.controller.ts +++ b/backend/src/modules/users/controller/users.controller.ts @@ -1,8 +1,11 @@ -import { Controller, Get, Inject, UseGuards } from '@nestjs/common'; +import { BadRequestException, Body, Controller, Get, Inject, Put, UseGuards } from '@nestjs/common'; import { ApiBadRequestResponse, ApiBearerAuth, + ApiBody, + ApiForbiddenResponse, ApiInternalServerErrorResponse, + ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, @@ -12,10 +15,17 @@ import JwtAuthenticationGuard from 'src/libs/guards/jwtAuth.guard'; import { BadRequestResponse } from 'src/libs/swagger/errors/bad-request.swagger'; import { InternalServerErrorResponse } from 'src/libs/swagger/errors/internal-server-error.swagger'; import { UnauthorizedResponse } from 'src/libs/swagger/errors/unauthorized.swagger'; +import UpdateUserDto from '../dto/update.user.dto'; import UserDto from '../dto/user.dto'; import { GetUserApplication } from '../interfaces/applications/get.user.application.interface'; +import { UpdateUserApplication } from '../interfaces/applications/update.user.service.interface'; import { TYPES } from '../interfaces/types'; import { UsersWithTeamsResponse } from '../swagger/users-with-teams.swagger'; +import { UPDATE_FAILED } from 'src/libs/exceptions/messages'; +import { SuperAdminGuard } from 'src/libs/guards/superAdmin.guard'; +import { ForbiddenResponse } from '../../../libs/swagger/errors/forbidden.swagger'; +import { NotFoundResponse } from '../../../libs/swagger/errors/not-found.swagger'; +import { UpdateSuperAdminSwagger } from '../swagger/update.superadmin.swagger'; @ApiBearerAuth('access-token') @ApiTags('Users') @@ -24,7 +34,9 @@ import { UsersWithTeamsResponse } from '../swagger/users-with-teams.swagger'; export default class UsersController { constructor( @Inject(TYPES.applications.GetUserApplication) - private getUserApp: GetUserApplication + private getUserApp: GetUserApplication, + @Inject(TYPES.applications.UpdateUserApplication) + private updateUserApp: UpdateUserApplication ) {} @ApiOperation({ summary: 'Retrieve a list of existing users' }) @@ -68,4 +80,44 @@ export default class UsersController { getAllUsersWithTeams() { return this.getUserApp.getUsersOnlyWithTeams(); } + + @ApiOperation({ summary: 'Update user is super admin' }) + @ApiBody({ type: UpdateSuperAdminSwagger }) + @ApiOkResponse({ + description: 'User successfully updated!', + type: UserDto + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized', + type: UnauthorizedResponse + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + type: BadRequestResponse + }) + @ApiInternalServerErrorResponse({ + description: 'Internal Server Error', + type: InternalServerErrorResponse + }) + @ApiNotFoundResponse({ + type: NotFoundResponse, + description: 'Not found!' + }) + @ApiForbiddenResponse({ + description: 'Forbidden', + type: ForbiddenResponse + }) + @ApiInternalServerErrorResponse({ + description: 'Internal Server Error', + type: InternalServerErrorResponse + }) + @UseGuards(SuperAdminGuard) + @Put('/sadmin') + async updateUserSuperAdmin(@Body() userData: UpdateUserDto) { + const user = await this.updateUserApp.updateSuperAdmin(userData); + + if (!user) throw new BadRequestException(UPDATE_FAILED); + + return user; + } } diff --git a/backend/src/modules/users/dto/update.user.dto.ts b/backend/src/modules/users/dto/update.user.dto.ts new file mode 100644 index 000000000..f1b0bbed6 --- /dev/null +++ b/backend/src/modules/users/dto/update.user.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsMongoId, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export default class UpdateUserDto { + @ApiProperty() + @IsNotEmpty() + @IsMongoId() + @IsString() + @IsMongoId() + _id!: string; + + @ApiProperty() + @IsOptional() + @IsString() + firstName?: string; + + @ApiProperty() + @IsOptional() + @IsString() + lastName?: string; + + @ApiProperty() + @IsOptional() + @IsString() + email?: string; + + @ApiPropertyOptional({ default: false }) + @IsOptional() + isSAdmin?: boolean; +} diff --git a/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts b/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts index 7dbe6b55a..8404063ca 100644 --- a/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts @@ -1,5 +1,6 @@ import { LeanDocument } from 'mongoose'; -import User, { UserDocument } from '../../entities/user.schema'; +import UpdateUserDto from '../../dto/update.user.dto'; +import { UserDocument } from '../../schemas/user.schema'; export interface UpdateUserApplication { setCurrentRefreshToken( @@ -11,7 +12,9 @@ export interface UpdateUserApplication { userEmail: string, newPassword: string, newPasswordConf: string - ): Promise; + ): Promise; checkEmail(token: string): Promise; + + updateSuperAdmin(user: UpdateUserDto): Promise>; } diff --git a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts index 088193869..144790185 100644 --- a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts @@ -1,13 +1,17 @@ -import User from '../../entities/user.schema'; +import { LeanDocument } from 'mongoose'; +import UpdateUserDto from '../../dto/update.user.dto'; +import { UserDocument } from '../../schemas/user.schema'; export interface UpdateUserService { - setCurrentRefreshToken(refreshToken: string, userId: string): Promise; + setCurrentRefreshToken(refreshToken: string, userId: string): Promise; setPassword( userEmail: string, newPassword: string, newPasswordConf: string - ): Promise; + ): Promise; checkEmail(token: string): Promise; + + updateSuperAdmin(user: UpdateUserDto): Promise>; } diff --git a/backend/src/modules/users/services/update.user.service.ts b/backend/src/modules/users/services/update.user.service.ts index 13cafed72..5149a7553 100644 --- a/backend/src/modules/users/services/update.user.service.ts +++ b/backend/src/modules/users/services/update.user.service.ts @@ -5,6 +5,7 @@ import { encrypt } from 'src/libs/utils/bcrypt'; import ResetPassword, { ResetPasswordDocument } from 'src/modules/auth/schemas/reset-password.schema'; +import UpdateUserDto from '../dto/update.user.dto'; import { UpdateUserService } from '../interfaces/services/update.user.service.interface'; import { TYPES } from '../interfaces/types'; import { UserRepositoryInterface } from '../repository/user.repository.interface'; @@ -55,4 +56,11 @@ export default class updateUserServiceImpl implements UpdateUserService { throw new HttpException('EXPIRED_TOKEN', HttpStatus.BAD_REQUEST); } } + + updateSuperAdmin(user: UpdateUserDto) { + return this.userModel + .findOneAndUpdate({ _id: user._id }, { $set: { isSAdmin: user.isSAdmin } }, { new: true }) + .lean() + .exec(); + } } diff --git a/backend/src/modules/users/swagger/update.superadmin.swagger.ts b/backend/src/modules/users/swagger/update.superadmin.swagger.ts new file mode 100644 index 000000000..49f2a4629 --- /dev/null +++ b/backend/src/modules/users/swagger/update.superadmin.swagger.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdateSuperAdminSwagger { + @ApiProperty() + id!: string; + + @ApiProperty() + isSAdmin!: boolean; +} diff --git a/frontend/src/api/userService.tsx b/frontend/src/api/userService.tsx index dbff8fc37..0eb01622d 100644 --- a/frontend/src/api/userService.tsx +++ b/frontend/src/api/userService.tsx @@ -1,7 +1,7 @@ import { GetServerSidePropsContext } from 'next'; import fetchData from '@/utils/fetchData'; -import { User, UserWithTeams } from '../types/user/user'; +import { User, UserWithTeams, UpdateUserIsAdmin } from '../types/user/user'; export const getAllUsers = (context?: GetServerSidePropsContext): Promise => fetchData(`/users`, { context, serverSide: !!context }); @@ -9,3 +9,6 @@ export const getAllUsers = (context?: GetServerSidePropsContext): Promise => fetchData(`/users/teams`, { context, serverSide: !!context }); + +export const updateUserIsAdminRequest = (user: UpdateUserIsAdmin): Promise => + fetchData(`/users/sadmin/`, { method: 'PUT', data: user }); diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx index 7307592ee..68045bc9c 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx @@ -8,7 +8,7 @@ import Box from '@/components/Primitives/Box'; import Flex from '@/components/Primitives/Flex'; import Text from '@/components/Primitives/Text'; import { UserWithTeams } from '@/types/user/user'; -// eslint-disable-next-line import/no-named-as-default +import Tooltip from '@/components/Primitives/Tooltip'; import SuperAdmin from './SuperAdmin'; import CardEnd from './CardEnd'; import CardTitle from './CardTitle'; @@ -32,7 +32,8 @@ const CardBody = React.memo(({ userWithTeams }) => { const loggedUserIsSAdmin = session?.user.isSAdmin; - const { firstName, lastName, email, isSAdmin } = userWithTeams.user; + // eslint-disable-next-line @typescript-eslint/naming-convention + const { firstName, lastName, email, isSAdmin, _id } = userWithTeams.user; const { teamsNames } = userWithTeams; const getTeamsCountText = () => { @@ -45,6 +46,16 @@ const CardBody = React.memo(({ userWithTeams }) => { return 'no teams'; }; + let teamsSeparatedByComma: string[] = []; + if (teamsNames) { + teamsSeparatedByComma = teamsNames?.map((team, index) => { + if (index !== teamsNames.length - 1) { + return team.concat(', '); + } + return team; + }); + } + return ( @@ -72,15 +83,21 @@ const CardBody = React.memo(({ userWithTeams }) => { - - {getTeamsCountText()} - + + + {getTeamsCountText()} + + {loggedUserIsSAdmin && ( - + )} {loggedUserIsSAdmin && } diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/EditUser.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/EditUser.tsx index 8c4c1de70..4c71b6f13 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/EditUser.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/EditUser.tsx @@ -2,20 +2,23 @@ import Icon from '@/components/icons/Icon'; import Flex from '@/components/Primitives/Flex'; import Tooltip from '@/components/Primitives/Tooltip'; import { User } from '@/types/user/user'; +import Link from 'next/link'; type EditUserProps = { user: User }; -const EditUser: React.FC = () => ( +const EditUser: React.FC = ({ user }) => ( - + + + ); diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx index a453eb505..7f68e048c 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx @@ -1,45 +1,45 @@ import Flex from '@/components/Primitives/Flex'; import Text from '@/components/Primitives/Text'; -import { Switch, SwitchThumb } from '@/components/Primitives/Switch'; -import Icon from '@/components/icons/Icon'; import { useState } from 'react'; +import { ConfigurationSettings } from '@/components/Board/Settings/partials/ConfigurationSettings'; import Separator from '@/components/Primitives/Separator'; +import useUser from '@/hooks/useUser'; +import { UpdateUserIsAdmin } from '@/types/user/user'; type SuperAdminProps = { userSAdmin: boolean; loggedUserSAdmin: boolean | undefined; + userId: string; }; -const SuperAdmin = ({ userSAdmin, loggedUserSAdmin }: SuperAdminProps) => { +const SuperAdmin = ({ userSAdmin, loggedUserSAdmin, userId }: SuperAdminProps) => { const [checkedState, setCheckedState] = useState(userSAdmin); - const handleSuperAdminChange = () => { - setCheckedState(!checkedState); - }; + const { + updateUserIsAdmin: { mutate }, + } = useUser(); + + const handleSuperAdminChange = (checked: boolean) => { + const updateTeamUser: UpdateUserIsAdmin = { + _id: userId, + isSAdmin: checked, + }; + setCheckedState(checked); + mutate(updateTeamUser); + }; if (loggedUserSAdmin) { return ( - - - {checkedState && ( - - - - )} - {!checkedState && } - - - Super Admin - + + + { SUPER ADMIN )} ->>>>>>> bd6d377 (feat: member only sees user teams count and which user is an admin) ); }; diff --git a/frontend/src/components/Users/UsersList/partials/ListOfCards/index.tsx b/frontend/src/components/Users/UsersList/partials/ListOfCards/index.tsx index 3d420d847..cd5cccb81 100644 --- a/frontend/src/components/Users/UsersList/partials/ListOfCards/index.tsx +++ b/frontend/src/components/Users/UsersList/partials/ListOfCards/index.tsx @@ -20,7 +20,7 @@ const ListOfCards = React.memo(({ isLoading }) => { <> - {usersWithTeams?.length} registered users + {usersWithTeams.length} registered users diff --git a/frontend/src/hooks/useUser.tsx b/frontend/src/hooks/useUser.tsx index 53d6a02aa..12cf21142 100644 --- a/frontend/src/hooks/useUser.tsx +++ b/frontend/src/hooks/useUser.tsx @@ -12,8 +12,13 @@ import { UseUserType, } from '@/types/user/user'; import { DASHBOARD_ROUTE } from '@/utils/routes'; +import { ToastStateEnum } from '@/utils/enums/toast-types'; +import { updateUserIsAdminRequest } from '../api/userService'; +import useUserUtils from './useUserUtils'; const useUser = (): UseUserType => { + const { setToastState, usersWithTeamsList, setUsersWithTeamsList, queryClient } = useUserUtils(); + const resetToken = useMutation( (emailUser: EmailUser) => resetTokenEmail(emailUser), { @@ -37,7 +42,33 @@ const useUser = (): UseUserType => { }); }; - return { loginAzure, resetToken, resetPassword }; + const updateUserIsAdmin = useMutation(updateUserIsAdminRequest, { + onSuccess: (data) => { + queryClient.invalidateQueries('usersWithTeams'); + + // updates the usersList recoil + const users = usersWithTeamsList.map((user) => + user.user._id === data._id ? { ...user, isAdmin: data.isSAdmin } : user, + ); + + setUsersWithTeamsList(users); + + setToastState({ + open: true, + content: 'The team user was successfully updated.', + type: ToastStateEnum.SUCCESS, + }); + }, + onError: () => { + setToastState({ + open: true, + content: 'Error while updating the team user', + type: ToastStateEnum.ERROR, + }); + }, + }); + + return { loginAzure, resetToken, resetPassword, updateUserIsAdmin }; }; export default useUser; diff --git a/frontend/src/hooks/useUserUtils.tsx b/frontend/src/hooks/useUserUtils.tsx new file mode 100644 index 000000000..ec26f4b50 --- /dev/null +++ b/frontend/src/hooks/useUserUtils.tsx @@ -0,0 +1,43 @@ +import { QueryClient, useQueryClient } from 'react-query'; +import { NextRouter, useRouter } from 'next/router'; +import { useSession } from 'next-auth/react'; +import { SetterOrUpdater, useRecoilState, useSetRecoilState } from 'recoil'; + +import { toastState } from '@/store/toast/atom/toast.atom'; +import { UserWithTeams } from '@/types/user/user'; +import { usersWithTeamsState } from '../store/user/atoms/user.atom'; +import { ToastStateEnum } from '../utils/enums/toast-types'; + +type UserUtilsType = { + userId: string; + queryClient: QueryClient; + setToastState: SetterOrUpdater<{ open: boolean; type: ToastStateEnum; content: string }>; + router: NextRouter; + usersWithTeamsList: UserWithTeams[]; + setUsersWithTeamsList: SetterOrUpdater; +}; + +const useUserUtils = (): UserUtilsType => { + const router = useRouter(); + const { data: session } = useSession({ required: false }); + + const queryClient = useQueryClient(); + + let userId = ''; + + if (session) userId = session.user.id; + + const setToastState = useSetRecoilState(toastState); + const [usersWithTeamsList, setUsersWithTeamsList] = useRecoilState(usersWithTeamsState); + + return { + userId, + queryClient, + setToastState, + router, + usersWithTeamsList, + setUsersWithTeamsList, + }; +}; + +export default useUserUtils; diff --git a/frontend/src/pages/users/[userId].tsx b/frontend/src/pages/users/[userId].tsx new file mode 100644 index 000000000..b3e7dcd58 --- /dev/null +++ b/frontend/src/pages/users/[userId].tsx @@ -0,0 +1,3 @@ +const UserDetails = () =>

User Details

; + +export default UserDetails; diff --git a/frontend/src/store/user/atoms/user.atom.tsx b/frontend/src/store/user/atoms/user.atom.tsx index 881eef024..580d159e1 100644 --- a/frontend/src/store/user/atoms/user.atom.tsx +++ b/frontend/src/store/user/atoms/user.atom.tsx @@ -12,7 +12,7 @@ export const userState = atom({ }, }); -export const usersWithTeamsState = atom({ +export const usersWithTeamsState = atom({ key: 'usersWithTeams', default: [], }); diff --git a/frontend/src/types/user/user.ts b/frontend/src/types/user/user.ts index f7c02c5bf..c6ed75270 100644 --- a/frontend/src/types/user/user.ts +++ b/frontend/src/types/user/user.ts @@ -21,6 +21,7 @@ export interface UseUserType { loginAzure: () => Promise; resetToken: UseMutationResult; resetPassword: UseMutationResult; + updateUserIsAdmin: UseMutationResult; } export interface LoginUser { @@ -58,4 +59,9 @@ export interface UserWithTeams { teamsNames?: string[]; } +export interface UpdateUserIsAdmin { + _id: string; + isSAdmin: boolean; +} + export type UserZod = 'name' | 'email' | 'password' | 'passwordConf'; From 1fc63c3f07fbefdb4205ad45ad874b33e288ed40 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Mon, 5 Dec 2022 10:51:33 +0000 Subject: [PATCH 05/16] chore: change behaviour of switch when error occurs when changing userSAdmin --- .../Users/UsersList/partials/CardUser/CardBody.tsx | 7 ++++++- .../Users/UsersList/partials/CardUser/SuperAdmin.tsx | 12 ++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx index 68045bc9c..ababa45f9 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx @@ -9,6 +9,7 @@ import Flex from '@/components/Primitives/Flex'; import Text from '@/components/Primitives/Text'; import { UserWithTeams } from '@/types/user/user'; import Tooltip from '@/components/Primitives/Tooltip'; +import Link from 'next/link'; import SuperAdmin from './SuperAdmin'; import CardEnd from './CardEnd'; import CardTitle from './CardTitle'; @@ -70,7 +71,11 @@ const CardBody = React.memo(({ userWithTeams }) => { /> - + + + + + diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx index 7f68e048c..b23e28056 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx @@ -17,17 +17,21 @@ const SuperAdmin = ({ userSAdmin, loggedUserSAdmin, userId }: SuperAdminProps) = const [checkedState, setCheckedState] = useState(userSAdmin); const { - updateUserIsAdmin: { mutate }, + updateUserIsAdmin: { mutateAsync }, } = useUser(); - const handleSuperAdminChange = (checked: boolean) => { + const handleSuperAdminChange = async (checked: boolean) => { const updateTeamUser: UpdateUserIsAdmin = { _id: userId, isSAdmin: checked, }; - setCheckedState(checked); - mutate(updateTeamUser); + try { + await mutateAsync(updateTeamUser); + setCheckedState(checked); + } catch (error) { + setCheckedState(!checked); + } }; if (loggedUserSAdmin) { From b1440961e5a9f01bb5e50a5bf7eb1b53bb216d8b Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Tue, 6 Dec 2022 12:11:45 +0000 Subject: [PATCH 06/16] chore: changed style of switch of user that isSAdmin, added tooltip, and auto-refresh from page --- .../partials/ConfigurationSettings/index.tsx | 60 ++++++++++++++----- frontend/src/components/Primitives/Switch.tsx | 6 ++ .../UsersList/partials/CardUser/CardBody.tsx | 16 ++--- .../UsersList/partials/CardUser/CardEnd.tsx | 9 +++ .../partials/CardUser/SuperAdmin.tsx | 37 +++++++----- frontend/src/pages/users/index.tsx | 13 ++-- 6 files changed, 94 insertions(+), 47 deletions(-) diff --git a/frontend/src/components/Board/Settings/partials/ConfigurationSettings/index.tsx b/frontend/src/components/Board/Settings/partials/ConfigurationSettings/index.tsx index 1f223ef2e..34c9d991c 100644 --- a/frontend/src/components/Board/Settings/partials/ConfigurationSettings/index.tsx +++ b/frontend/src/components/Board/Settings/partials/ConfigurationSettings/index.tsx @@ -4,6 +4,7 @@ import Icon from '@/components/icons/Icon'; import Flex from '@/components/Primitives/Flex'; import { Switch, SwitchThumb } from '@/components/Primitives/Switch'; import Text from '@/components/Primitives/Text'; +import Tooltip from '@/components/Primitives/Tooltip'; type Props = { title: string; @@ -12,6 +13,8 @@ type Props = { handleCheckedChange: (checked: boolean) => void; children?: ReactNode; color?: string; + disabled?: boolean; + styleVariant?: boolean; }; const ConfigurationSettings = ({ @@ -21,22 +24,51 @@ const ConfigurationSettings = ({ handleCheckedChange, children, color, + disabled, + styleVariant, }: Props) => ( - - - {isChecked && ( - - )} - - + {styleVariant && ( + + + + + {isChecked && ( + + )} + + + + + )} + {!styleVariant && ( + + + {isChecked && ( + + )} + + + )} {title} diff --git a/frontend/src/components/Primitives/Switch.tsx b/frontend/src/components/Primitives/Switch.tsx index bbffc61f4..1415bc0cc 100644 --- a/frontend/src/components/Primitives/Switch.tsx +++ b/frontend/src/components/Primitives/Switch.tsx @@ -22,6 +22,12 @@ const StyledSwitch = styled(SwitchPrimitive.Root, { height: '$20', }, md: { flex: '0 0 $sizes$42', width: '$42', height: '$24' }, + disabled: { + '&[data-state="checked"]': { backgroundColor: '$successBase', opacity: 0.5 }, + flex: '0 0 $sizes$35', + width: '$35', + height: '$20', + }, }, }, defaultVariants: { diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx index ababa45f9..357d1482d 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx @@ -32,6 +32,7 @@ const CardBody = React.memo(({ userWithTeams }) => { const { data: session } = useSession(); const loggedUserIsSAdmin = session?.user.isSAdmin; + const userLoginId = session?.user.id; // eslint-disable-next-line @typescript-eslint/naming-convention const { firstName, lastName, email, isSAdmin, _id } = userWithTeams.user; @@ -89,7 +90,7 @@ const CardBody = React.memo(({ userWithTeams }) => { - + {getTeamsCountText()} @@ -97,13 +98,12 @@ const CardBody = React.memo(({ userWithTeams }) => { - {loggedUserIsSAdmin && ( - - )} + {loggedUserIsSAdmin && } diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/CardEnd.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/CardEnd.tsx index f6326bf8f..e6bddc8e1 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/CardEnd.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/CardEnd.tsx @@ -1,6 +1,7 @@ import React from 'react'; import Flex from '@/components/Primitives/Flex'; +import Separator from '@/components/Primitives/Separator'; import { User } from '@/types/user/user'; import DeleteUser from './DeleteUser'; import EditUser from './EditUser'; @@ -11,6 +12,14 @@ type CardEndProps = { const CardEnd: React.FC = React.memo(({ user }) => ( + diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx index b23e28056..522a8464e 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx @@ -3,7 +3,6 @@ import Text from '@/components/Primitives/Text'; import { useState } from 'react'; import { ConfigurationSettings } from '@/components/Board/Settings/partials/ConfigurationSettings'; -import Separator from '@/components/Primitives/Separator'; import useUser from '@/hooks/useUser'; import { UpdateUserIsAdmin } from '@/types/user/user'; @@ -11,10 +10,12 @@ type SuperAdminProps = { userSAdmin: boolean; loggedUserSAdmin: boolean | undefined; userId: string; + loggedUserId: string | undefined; }; -const SuperAdmin = ({ userSAdmin, loggedUserSAdmin, userId }: SuperAdminProps) => { +const SuperAdmin = ({ userSAdmin, loggedUserSAdmin, userId, loggedUserId }: SuperAdminProps) => { const [checkedState, setCheckedState] = useState(userSAdmin); + const isDisabledState = true; const { updateUserIsAdmin: { mutateAsync }, @@ -37,21 +38,25 @@ const SuperAdmin = ({ userSAdmin, loggedUserSAdmin, userId }: SuperAdminProps) = if (loggedUserSAdmin) { return ( - + {loggedUserId !== userId && ( + + )} - + {loggedUserId === userId && ( + + )} ); } diff --git a/frontend/src/pages/users/index.tsx b/frontend/src/pages/users/index.tsx index 9022fc23d..805aaea6c 100644 --- a/frontend/src/pages/users/index.tsx +++ b/frontend/src/pages/users/index.tsx @@ -1,4 +1,4 @@ -import { ReactElement, Suspense, useEffect } from 'react'; +import { ReactElement, Suspense } from 'react'; import { dehydrate, QueryClient, useQuery } from 'react-query'; import { GetServerSideProps, GetServerSidePropsContext } from 'next'; import { useSession } from 'next-auth/react'; @@ -21,7 +21,7 @@ const Users = () => { const setUsersWithTeamsState = useSetRecoilState(usersWithTeamsState); const { data, isFetching } = useQuery(['usersWithTeams'], () => getAllUsersWithTeams(), { - enabled: true, + enabled: false, refetchOnWindowFocus: false, onError: () => { setToastState({ @@ -32,14 +32,9 @@ const Users = () => { }, }); - useEffect(() => { - if (!data) { - return; - } - + if (data) { setUsersWithTeamsState(data); - }, [data, setUsersWithTeamsState]); - + } if (!session || !data) return null; return ( From 9dd712ee7ec11edc7736d4ea94d103c65fd1c8b2 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Tue, 6 Dec 2022 13:16:14 +0000 Subject: [PATCH 07/16] refactor: fix conflicts --- .../applications/update.user.service.interface.ts | 4 ++-- .../interfaces/services/update.user.service.interface.ts | 6 +++--- backend/src/modules/users/services/update.user.service.ts | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts b/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts index 8404063ca..95cc7f619 100644 --- a/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts @@ -1,6 +1,6 @@ import { LeanDocument } from 'mongoose'; import UpdateUserDto from '../../dto/update.user.dto'; -import { UserDocument } from '../../schemas/user.schema'; +import User, { UserDocument } from '../../entities/user.schema'; export interface UpdateUserApplication { setCurrentRefreshToken( @@ -12,7 +12,7 @@ export interface UpdateUserApplication { userEmail: string, newPassword: string, newPasswordConf: string - ): Promise; + ): Promise; checkEmail(token: string): Promise; diff --git a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts index 144790185..9d24f8e50 100644 --- a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts @@ -1,15 +1,15 @@ import { LeanDocument } from 'mongoose'; import UpdateUserDto from '../../dto/update.user.dto'; -import { UserDocument } from '../../schemas/user.schema'; +import User, { UserDocument } from '../../entities/user.schema'; export interface UpdateUserService { - setCurrentRefreshToken(refreshToken: string, userId: string): Promise; + setCurrentRefreshToken(refreshToken: string, userId: string): Promise; setPassword( userEmail: string, newPassword: string, newPasswordConf: string - ): Promise; + ): Promise; checkEmail(token: string): Promise; diff --git a/backend/src/modules/users/services/update.user.service.ts b/backend/src/modules/users/services/update.user.service.ts index 5149a7553..48a7562b5 100644 --- a/backend/src/modules/users/services/update.user.service.ts +++ b/backend/src/modules/users/services/update.user.service.ts @@ -9,10 +9,12 @@ import UpdateUserDto from '../dto/update.user.dto'; import { UpdateUserService } from '../interfaces/services/update.user.service.interface'; import { TYPES } from '../interfaces/types'; import { UserRepositoryInterface } from '../repository/user.repository.interface'; +import User, { UserDocument } from '../entities/user.schema'; @Injectable() export default class updateUserServiceImpl implements UpdateUserService { constructor( + @InjectModel(User.name) private userModel: Model, @Inject(TYPES.repository) private readonly userRepository: UserRepositoryInterface, @InjectModel(ResetPassword.name) From 1d6f580f3e99f9a629cc4a996511b35107913a3a Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Tue, 6 Dec 2022 15:16:55 +0000 Subject: [PATCH 08/16] test: fix test --- frontend/src/__tests__/index.test.tsx | 5 ++++- frontend/src/hooks/useUserUtils.tsx | 9 +-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/frontend/src/__tests__/index.test.tsx b/frontend/src/__tests__/index.test.tsx index 473966fd4..696bde577 100644 --- a/frontend/src/__tests__/index.test.tsx +++ b/frontend/src/__tests__/index.test.tsx @@ -1,12 +1,15 @@ import { QueryClient, QueryClientProvider } from 'react-query'; import React from 'react'; import { render } from '@testing-library/react'; +import { RecoilRoot } from 'recoil'; import Home from '../pages'; const queryClient = new QueryClient(); export const Wrapper: React.FC = ({ children }) => ( - {children} + + {children} + ); jest.mock('next/config', () => () => ({ diff --git a/frontend/src/hooks/useUserUtils.tsx b/frontend/src/hooks/useUserUtils.tsx index ec26f4b50..d05816a5e 100644 --- a/frontend/src/hooks/useUserUtils.tsx +++ b/frontend/src/hooks/useUserUtils.tsx @@ -1,6 +1,6 @@ import { QueryClient, useQueryClient } from 'react-query'; import { NextRouter, useRouter } from 'next/router'; -import { useSession } from 'next-auth/react'; + import { SetterOrUpdater, useRecoilState, useSetRecoilState } from 'recoil'; import { toastState } from '@/store/toast/atom/toast.atom'; @@ -9,7 +9,6 @@ import { usersWithTeamsState } from '../store/user/atoms/user.atom'; import { ToastStateEnum } from '../utils/enums/toast-types'; type UserUtilsType = { - userId: string; queryClient: QueryClient; setToastState: SetterOrUpdater<{ open: boolean; type: ToastStateEnum; content: string }>; router: NextRouter; @@ -19,19 +18,13 @@ type UserUtilsType = { const useUserUtils = (): UserUtilsType => { const router = useRouter(); - const { data: session } = useSession({ required: false }); const queryClient = useQueryClient(); - let userId = ''; - - if (session) userId = session.user.id; - const setToastState = useSetRecoilState(toastState); const [usersWithTeamsList, setUsersWithTeamsList] = useRecoilState(usersWithTeamsState); return { - userId, queryClient, setToastState, router, From ea8fb9bf5e55d496f7dd36d5bda610185b037dea Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Wed, 7 Dec 2022 11:00:55 +0000 Subject: [PATCH 09/16] refactor: updated updateUserSAdmin endpoint to use useRepository and resolved sugested changes on PR --- backend/src/libs/guards/superAdmin.guard.ts | 4 +--- .../interfaces/base.repository.interface.ts | 8 ++++++-- .../repositories/mongo/mongo-generic.repository.ts | 10 +++++++--- .../src/modules/users/controller/users.controller.ts | 9 ++------- .../users/repository/user.repository.interface.ts | 1 + .../src/modules/users/repository/user.repository.ts | 4 ++++ .../src/modules/users/services/update.user.service.ts | 9 +++++---- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/backend/src/libs/guards/superAdmin.guard.ts b/backend/src/libs/guards/superAdmin.guard.ts index 2277566b2..c24ee21a9 100644 --- a/backend/src/libs/guards/superAdmin.guard.ts +++ b/backend/src/libs/guards/superAdmin.guard.ts @@ -5,10 +5,8 @@ export class SuperAdminGuard implements CanActivate { async canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); - const user = request.user; - try { - return user.isSAdmin; + return request.user.isSAdmin; } catch (error) { throw new ForbiddenException(); } diff --git a/backend/src/libs/repositories/interfaces/base.repository.interface.ts b/backend/src/libs/repositories/interfaces/base.repository.interface.ts index 6fdca55fb..63fc87121 100644 --- a/backend/src/libs/repositories/interfaces/base.repository.interface.ts +++ b/backend/src/libs/repositories/interfaces/base.repository.interface.ts @@ -1,4 +1,4 @@ -import { UpdateQuery } from 'mongoose'; +import { QueryOptions, UpdateQuery } from 'mongoose'; import { ModelProps, SelectedValues } from '../types'; export interface BaseInterfaceRepository { @@ -14,5 +14,9 @@ export interface BaseInterfaceRepository { countDocuments(): Promise; - findOneByFieldAndUpdate(value: ModelProps, query: UpdateQuery): Promise; + findOneByFieldAndUpdate( + value: ModelProps, + query: UpdateQuery, + options?: QueryOptions + ): Promise; } diff --git a/backend/src/libs/repositories/mongo/mongo-generic.repository.ts b/backend/src/libs/repositories/mongo/mongo-generic.repository.ts index 8d08af18a..01c242f18 100644 --- a/backend/src/libs/repositories/mongo/mongo-generic.repository.ts +++ b/backend/src/libs/repositories/mongo/mongo-generic.repository.ts @@ -1,4 +1,4 @@ -import { Model, UpdateQuery } from 'mongoose'; +import { Model, QueryOptions, UpdateQuery } from 'mongoose'; import { BaseInterfaceRepository } from '../interfaces/base.repository.interface'; import { ModelProps, SelectedValues } from '../types'; @@ -39,7 +39,11 @@ export class MongoGenericRepository implements BaseInterfaceRepository { return this._repository.countDocuments().exec(); } - findOneByFieldAndUpdate(value: ModelProps, query: UpdateQuery): Promise { - return this._repository.findOneAndUpdate(value, query).exec(); + findOneByFieldAndUpdate( + value: ModelProps, + query: UpdateQuery, + options?: QueryOptions + ): Promise { + return this._repository.findOneAndUpdate(value, query, options).exec(); } } diff --git a/backend/src/modules/users/controller/users.controller.ts b/backend/src/modules/users/controller/users.controller.ts index 2d17fd565..ffb444252 100644 --- a/backend/src/modules/users/controller/users.controller.ts +++ b/backend/src/modules/users/controller/users.controller.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Body, Controller, Get, Inject, Put, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Inject, Put, UseGuards } from '@nestjs/common'; import { ApiBadRequestResponse, ApiBearerAuth, @@ -21,7 +21,6 @@ import { GetUserApplication } from '../interfaces/applications/get.user.applicat import { UpdateUserApplication } from '../interfaces/applications/update.user.service.interface'; import { TYPES } from '../interfaces/types'; import { UsersWithTeamsResponse } from '../swagger/users-with-teams.swagger'; -import { UPDATE_FAILED } from 'src/libs/exceptions/messages'; import { SuperAdminGuard } from 'src/libs/guards/superAdmin.guard'; import { ForbiddenResponse } from '../../../libs/swagger/errors/forbidden.swagger'; import { NotFoundResponse } from '../../../libs/swagger/errors/not-found.swagger'; @@ -114,10 +113,6 @@ export default class UsersController { @UseGuards(SuperAdminGuard) @Put('/sadmin') async updateUserSuperAdmin(@Body() userData: UpdateUserDto) { - const user = await this.updateUserApp.updateSuperAdmin(userData); - - if (!user) throw new BadRequestException(UPDATE_FAILED); - - return user; + return await this.updateUserApp.updateSuperAdmin(userData); } } diff --git a/backend/src/modules/users/repository/user.repository.interface.ts b/backend/src/modules/users/repository/user.repository.interface.ts index 90e125a15..f94b105fc 100644 --- a/backend/src/modules/users/repository/user.repository.interface.ts +++ b/backend/src/modules/users/repository/user.repository.interface.ts @@ -5,4 +5,5 @@ export interface UserRepositoryInterface extends BaseInterfaceRepository { getById(userId: string): Promise; updateUserWithRefreshToken(refreshToken: string, userId: string): Promise; updateUserPassword(email: string, password: string): Promise; + updateSuperAdmin(userId: string, isSAdmin: boolean): Promise; } diff --git a/backend/src/modules/users/repository/user.repository.ts b/backend/src/modules/users/repository/user.repository.ts index 464141cfd..c63a12db8 100644 --- a/backend/src/modules/users/repository/user.repository.ts +++ b/backend/src/modules/users/repository/user.repository.ts @@ -33,4 +33,8 @@ export class UserRepository } ); } + + updateSuperAdmin(userId: string, isSAdmin: boolean) { + return this.findOneByFieldAndUpdate({ _id: userId }, { $set: { isSAdmin } }, { new: true }); + } } diff --git a/backend/src/modules/users/services/update.user.service.ts b/backend/src/modules/users/services/update.user.service.ts index 48a7562b5..c2b6eb012 100644 --- a/backend/src/modules/users/services/update.user.service.ts +++ b/backend/src/modules/users/services/update.user.service.ts @@ -60,9 +60,10 @@ export default class updateUserServiceImpl implements UpdateUserService { } updateSuperAdmin(user: UpdateUserDto) { - return this.userModel - .findOneAndUpdate({ _id: user._id }, { $set: { isSAdmin: user.isSAdmin } }, { new: true }) - .lean() - .exec(); + const userToUpdate = this.userRepository.updateSuperAdmin(user._id, user.isSAdmin); + + if (!userToUpdate) throw new HttpException('UPDATE_FAILED', HttpStatus.BAD_REQUEST); + + return userToUpdate; } } From 7a33587e33fe440d79985e453dc62e2c5c51c510 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Wed, 7 Dec 2022 13:17:57 +0000 Subject: [PATCH 10/16] refactor: applied PR requested changes --- backend/src/libs/guards/superAdmin.guard.ts | 8 ++------ backend/src/modules/users/controller/users.controller.ts | 4 ++-- backend/src/modules/users/services/update.user.service.ts | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/backend/src/libs/guards/superAdmin.guard.ts b/backend/src/libs/guards/superAdmin.guard.ts index c24ee21a9..2dd0acdb3 100644 --- a/backend/src/libs/guards/superAdmin.guard.ts +++ b/backend/src/libs/guards/superAdmin.guard.ts @@ -1,14 +1,10 @@ -import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; @Injectable() export class SuperAdminGuard implements CanActivate { async canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); - try { - return request.user.isSAdmin; - } catch (error) { - throw new ForbiddenException(); - } + return request.user.isSAdmin; } } diff --git a/backend/src/modules/users/controller/users.controller.ts b/backend/src/modules/users/controller/users.controller.ts index ffb444252..64ae9ac2e 100644 --- a/backend/src/modules/users/controller/users.controller.ts +++ b/backend/src/modules/users/controller/users.controller.ts @@ -112,7 +112,7 @@ export default class UsersController { }) @UseGuards(SuperAdminGuard) @Put('/sadmin') - async updateUserSuperAdmin(@Body() userData: UpdateUserDto) { - return await this.updateUserApp.updateSuperAdmin(userData); + updateUserSuperAdmin(@Body() userData: UpdateUserDto) { + return this.updateUserApp.updateSuperAdmin(userData); } } diff --git a/backend/src/modules/users/services/update.user.service.ts b/backend/src/modules/users/services/update.user.service.ts index c2b6eb012..1bb73635f 100644 --- a/backend/src/modules/users/services/update.user.service.ts +++ b/backend/src/modules/users/services/update.user.service.ts @@ -59,8 +59,8 @@ export default class updateUserServiceImpl implements UpdateUserService { } } - updateSuperAdmin(user: UpdateUserDto) { - const userToUpdate = this.userRepository.updateSuperAdmin(user._id, user.isSAdmin); + async updateSuperAdmin(user: UpdateUserDto) { + const userToUpdate = await this.userRepository.updateSuperAdmin(user._id, user.isSAdmin); if (!userToUpdate) throw new HttpException('UPDATE_FAILED', HttpStatus.BAD_REQUEST); From 0f23379b937fc602fa9c44895b981a8db21d2147 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Wed, 7 Dec 2022 15:17:34 +0000 Subject: [PATCH 11/16] refactor: applied PR requested changes --- backend/src/modules/users/services/update.user.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/modules/users/services/update.user.service.ts b/backend/src/modules/users/services/update.user.service.ts index 1bb73635f..b2c62c5f9 100644 --- a/backend/src/modules/users/services/update.user.service.ts +++ b/backend/src/modules/users/services/update.user.service.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { encrypt } from 'src/libs/utils/bcrypt'; @@ -10,6 +10,7 @@ import { UpdateUserService } from '../interfaces/services/update.user.service.in import { TYPES } from '../interfaces/types'; import { UserRepositoryInterface } from '../repository/user.repository.interface'; import User, { UserDocument } from '../entities/user.schema'; +import { UPDATE_FAILED } from 'src/libs/exceptions/messages'; @Injectable() export default class updateUserServiceImpl implements UpdateUserService { @@ -62,7 +63,7 @@ export default class updateUserServiceImpl implements UpdateUserService { async updateSuperAdmin(user: UpdateUserDto) { const userToUpdate = await this.userRepository.updateSuperAdmin(user._id, user.isSAdmin); - if (!userToUpdate) throw new HttpException('UPDATE_FAILED', HttpStatus.BAD_REQUEST); + if (!userToUpdate) throw new BadRequestException(UPDATE_FAILED); return userToUpdate; } From 807e4cf2e9d06a2d1c5af3c40b5e1a3aa6a97bd6 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Fri, 9 Dec 2022 11:12:08 +0000 Subject: [PATCH 12/16] refactor: applied suggested changes on PR and and added new validation to updateSuperAdmin endpoint --- .../users/applications/update.user.application.ts | 5 +++-- .../modules/users/controller/users.controller.ts | 7 ++++--- .../applications/update.user.service.interface.ts | 6 +++++- .../services/update.user.service.interface.ts | 6 +++++- .../modules/users/services/update.user.service.ts | 14 ++++++++++---- .../Users/UsersList/partials/CardUser/CardBody.tsx | 10 +--------- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/backend/src/modules/users/applications/update.user.application.ts b/backend/src/modules/users/applications/update.user.application.ts index 30304ac9f..b59567960 100644 --- a/backend/src/modules/users/applications/update.user.application.ts +++ b/backend/src/modules/users/applications/update.user.application.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; import UpdateUserDto from '../dto/update.user.dto'; import { UpdateUserApplication } from '../interfaces/applications/update.user.service.interface'; import { UpdateUserService } from '../interfaces/services/update.user.service.interface'; @@ -23,7 +24,7 @@ export class UpdateUserApplicationImpl implements UpdateUserApplication { return this.updateUserService.checkEmail(token); } - updateSuperAdmin(user: UpdateUserDto) { - return this.updateUserService.updateSuperAdmin(user); + updateSuperAdmin(user: UpdateUserDto, requestUser: RequestWithUser) { + return this.updateUserService.updateSuperAdmin(user, requestUser); } } diff --git a/backend/src/modules/users/controller/users.controller.ts b/backend/src/modules/users/controller/users.controller.ts index 64ae9ac2e..c59c1e52f 100644 --- a/backend/src/modules/users/controller/users.controller.ts +++ b/backend/src/modules/users/controller/users.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Inject, Put, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Inject, Put, Req, UseGuards } from '@nestjs/common'; import { ApiBadRequestResponse, ApiBearerAuth, @@ -25,6 +25,7 @@ import { SuperAdminGuard } from 'src/libs/guards/superAdmin.guard'; import { ForbiddenResponse } from '../../../libs/swagger/errors/forbidden.swagger'; import { NotFoundResponse } from '../../../libs/swagger/errors/not-found.swagger'; import { UpdateSuperAdminSwagger } from '../swagger/update.superadmin.swagger'; +import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; @ApiBearerAuth('access-token') @ApiTags('Users') @@ -112,7 +113,7 @@ export default class UsersController { }) @UseGuards(SuperAdminGuard) @Put('/sadmin') - updateUserSuperAdmin(@Body() userData: UpdateUserDto) { - return this.updateUserApp.updateSuperAdmin(userData); + updateUserSuperAdmin(@Req() request: RequestWithUser, @Body() userData: UpdateUserDto) { + return this.updateUserApp.updateSuperAdmin(userData, request); } } diff --git a/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts b/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts index 95cc7f619..1617b1a50 100644 --- a/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts @@ -1,6 +1,7 @@ import { LeanDocument } from 'mongoose'; import UpdateUserDto from '../../dto/update.user.dto'; import User, { UserDocument } from '../../entities/user.schema'; +import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; export interface UpdateUserApplication { setCurrentRefreshToken( @@ -16,5 +17,8 @@ export interface UpdateUserApplication { checkEmail(token: string): Promise; - updateSuperAdmin(user: UpdateUserDto): Promise>; + updateSuperAdmin( + user: UpdateUserDto, + requestUser: RequestWithUser + ): Promise>; } diff --git a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts index 9d24f8e50..0cd22256b 100644 --- a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts @@ -1,6 +1,7 @@ import { LeanDocument } from 'mongoose'; import UpdateUserDto from '../../dto/update.user.dto'; import User, { UserDocument } from '../../entities/user.schema'; +import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; export interface UpdateUserService { setCurrentRefreshToken(refreshToken: string, userId: string): Promise; @@ -13,5 +14,8 @@ export interface UpdateUserService { checkEmail(token: string): Promise; - updateSuperAdmin(user: UpdateUserDto): Promise>; + updateSuperAdmin( + user: UpdateUserDto, + requestUser: RequestWithUser + ): Promise>; } diff --git a/backend/src/modules/users/services/update.user.service.ts b/backend/src/modules/users/services/update.user.service.ts index b2c62c5f9..5e00aa52b 100644 --- a/backend/src/modules/users/services/update.user.service.ts +++ b/backend/src/modules/users/services/update.user.service.ts @@ -11,6 +11,7 @@ import { TYPES } from '../interfaces/types'; import { UserRepositoryInterface } from '../repository/user.repository.interface'; import User, { UserDocument } from '../entities/user.schema'; import { UPDATE_FAILED } from 'src/libs/exceptions/messages'; +import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; @Injectable() export default class updateUserServiceImpl implements UpdateUserService { @@ -60,11 +61,16 @@ export default class updateUserServiceImpl implements UpdateUserService { } } - async updateSuperAdmin(user: UpdateUserDto) { - const userToUpdate = await this.userRepository.updateSuperAdmin(user._id, user.isSAdmin); + async updateSuperAdmin(user: UpdateUserDto, requestUser: RequestWithUser) { + if (requestUser.user._id.toString() === user._id) { + throw new BadRequestException(UPDATE_FAILED); + } + const userUpdated = await this.userRepository.updateSuperAdmin(user._id, user.isSAdmin); - if (!userToUpdate) throw new BadRequestException(UPDATE_FAILED); + if (!userUpdated) { + throw new BadRequestException(UPDATE_FAILED); + } - return userToUpdate; + return userUpdated; } } diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx index 357d1482d..3e7e31ac8 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/CardBody.tsx @@ -48,15 +48,7 @@ const CardBody = React.memo(({ userWithTeams }) => { return 'no teams'; }; - let teamsSeparatedByComma: string[] = []; - if (teamsNames) { - teamsSeparatedByComma = teamsNames?.map((team, index) => { - if (index !== teamsNames.length - 1) { - return team.concat(', '); - } - return team; - }); - } + const teamsSeparatedByComma = teamsNames?.join(', ') || ''; return ( From 95628f1e9181df17cceb5a9ddf1abcc55ec036bc Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Fri, 9 Dec 2022 12:38:39 +0000 Subject: [PATCH 13/16] refactor: applied suggested changes on PR --- .../modules/users/applications/update.user.application.ts | 4 ++-- backend/src/modules/users/controller/users.controller.ts | 2 +- .../applications/update.user.service.interface.ts | 7 ++----- .../interfaces/services/update.user.service.interface.ts | 7 ++----- backend/src/modules/users/services/update.user.service.ts | 6 +++--- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/backend/src/modules/users/applications/update.user.application.ts b/backend/src/modules/users/applications/update.user.application.ts index b59567960..73a235497 100644 --- a/backend/src/modules/users/applications/update.user.application.ts +++ b/backend/src/modules/users/applications/update.user.application.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; -import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; import UpdateUserDto from '../dto/update.user.dto'; +import UserDto from '../dto/user.dto'; import { UpdateUserApplication } from '../interfaces/applications/update.user.service.interface'; import { UpdateUserService } from '../interfaces/services/update.user.service.interface'; import { TYPES } from '../interfaces/types'; @@ -24,7 +24,7 @@ export class UpdateUserApplicationImpl implements UpdateUserApplication { return this.updateUserService.checkEmail(token); } - updateSuperAdmin(user: UpdateUserDto, requestUser: RequestWithUser) { + updateSuperAdmin(user: UpdateUserDto, requestUser: UserDto) { return this.updateUserService.updateSuperAdmin(user, requestUser); } } diff --git a/backend/src/modules/users/controller/users.controller.ts b/backend/src/modules/users/controller/users.controller.ts index c59c1e52f..9e52e06ae 100644 --- a/backend/src/modules/users/controller/users.controller.ts +++ b/backend/src/modules/users/controller/users.controller.ts @@ -114,6 +114,6 @@ export default class UsersController { @UseGuards(SuperAdminGuard) @Put('/sadmin') updateUserSuperAdmin(@Req() request: RequestWithUser, @Body() userData: UpdateUserDto) { - return this.updateUserApp.updateSuperAdmin(userData, request); + return this.updateUserApp.updateSuperAdmin(userData, request.user); } } diff --git a/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts b/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts index 1617b1a50..c55d4fc4c 100644 --- a/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/applications/update.user.service.interface.ts @@ -1,7 +1,7 @@ import { LeanDocument } from 'mongoose'; import UpdateUserDto from '../../dto/update.user.dto'; import User, { UserDocument } from '../../entities/user.schema'; -import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; +import UserDto from '../../dto/user.dto'; export interface UpdateUserApplication { setCurrentRefreshToken( @@ -17,8 +17,5 @@ export interface UpdateUserApplication { checkEmail(token: string): Promise; - updateSuperAdmin( - user: UpdateUserDto, - requestUser: RequestWithUser - ): Promise>; + updateSuperAdmin(user: UpdateUserDto, requestUser: UserDto): Promise>; } diff --git a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts index 0cd22256b..3780a18a3 100644 --- a/backend/src/modules/users/interfaces/services/update.user.service.interface.ts +++ b/backend/src/modules/users/interfaces/services/update.user.service.interface.ts @@ -1,7 +1,7 @@ import { LeanDocument } from 'mongoose'; import UpdateUserDto from '../../dto/update.user.dto'; import User, { UserDocument } from '../../entities/user.schema'; -import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; +import UserDto from '../../dto/user.dto'; export interface UpdateUserService { setCurrentRefreshToken(refreshToken: string, userId: string): Promise; @@ -14,8 +14,5 @@ export interface UpdateUserService { checkEmail(token: string): Promise; - updateSuperAdmin( - user: UpdateUserDto, - requestUser: RequestWithUser - ): Promise>; + updateSuperAdmin(user: UpdateUserDto, requestUser: UserDto): Promise>; } diff --git a/backend/src/modules/users/services/update.user.service.ts b/backend/src/modules/users/services/update.user.service.ts index 5e00aa52b..be35f064f 100644 --- a/backend/src/modules/users/services/update.user.service.ts +++ b/backend/src/modules/users/services/update.user.service.ts @@ -11,7 +11,7 @@ import { TYPES } from '../interfaces/types'; import { UserRepositoryInterface } from '../repository/user.repository.interface'; import User, { UserDocument } from '../entities/user.schema'; import { UPDATE_FAILED } from 'src/libs/exceptions/messages'; -import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; +import UserDto from '../dto/user.dto'; @Injectable() export default class updateUserServiceImpl implements UpdateUserService { @@ -61,8 +61,8 @@ export default class updateUserServiceImpl implements UpdateUserService { } } - async updateSuperAdmin(user: UpdateUserDto, requestUser: RequestWithUser) { - if (requestUser.user._id.toString() === user._id) { + async updateSuperAdmin(user: UpdateUserDto, requestUser: UserDto) { + if (requestUser._id.toString() === user._id) { throw new BadRequestException(UPDATE_FAILED); } const userUpdated = await this.userRepository.updateSuperAdmin(user._id, user.isSAdmin); From add27be1e29ed4ac6a1ec4f6f1529651fdc50203 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Mon, 12 Dec 2022 17:30:38 +0000 Subject: [PATCH 14/16] refactor: applied changes requested on PR --- .../SwitchThumbComponent.tsx | 25 ++++++++++++++++ .../partials/ConfigurationSettings/index.tsx | 30 +++---------------- 2 files changed, 29 insertions(+), 26 deletions(-) create mode 100644 frontend/src/components/Board/Settings/partials/ConfigurationSettings/SwitchThumbComponent.tsx diff --git a/frontend/src/components/Board/Settings/partials/ConfigurationSettings/SwitchThumbComponent.tsx b/frontend/src/components/Board/Settings/partials/ConfigurationSettings/SwitchThumbComponent.tsx new file mode 100644 index 000000000..1371fa40d --- /dev/null +++ b/frontend/src/components/Board/Settings/partials/ConfigurationSettings/SwitchThumbComponent.tsx @@ -0,0 +1,25 @@ +import Icon from '@/components/icons/Icon'; +import { SwitchThumb } from '@/components/Primitives/Switch'; + +type Props = { + isChecked: boolean; + iconName: string; + color: string | undefined; +}; + +const SwitchThumbComponent = ({ isChecked, iconName, color }: Props) => ( + + {isChecked && ( + + )} + +); + +export default SwitchThumbComponent; diff --git a/frontend/src/components/Board/Settings/partials/ConfigurationSettings/index.tsx b/frontend/src/components/Board/Settings/partials/ConfigurationSettings/index.tsx index 34c9d991c..9ff182f44 100644 --- a/frontend/src/components/Board/Settings/partials/ConfigurationSettings/index.tsx +++ b/frontend/src/components/Board/Settings/partials/ConfigurationSettings/index.tsx @@ -1,10 +1,10 @@ import { ReactNode } from 'react'; -import Icon from '@/components/icons/Icon'; import Flex from '@/components/Primitives/Flex'; -import { Switch, SwitchThumb } from '@/components/Primitives/Switch'; +import { Switch } from '@/components/Primitives/Switch'; import Text from '@/components/Primitives/Text'; import Tooltip from '@/components/Primitives/Tooltip'; +import SwitchThumbComponent from './SwitchThumbComponent'; type Props = { title: string; @@ -37,36 +37,14 @@ const ConfigurationSettings = ({ onCheckedChange={handleCheckedChange} disabled={disabled} > - - {isChecked && ( - - )} - + )} {!styleVariant && ( - - {isChecked && ( - - )} - + )} From 8be659d2f5f7f31c2c4f1a7cca71d881ab6810bc Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Mon, 12 Dec 2022 18:04:31 +0000 Subject: [PATCH 15/16] refactor: resolved conflicts --- .../src/libs/repositories/mongo/mongo-generic.repository.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/libs/repositories/mongo/mongo-generic.repository.ts b/backend/src/libs/repositories/mongo/mongo-generic.repository.ts index 8c7555da9..e2d3a79ee 100644 --- a/backend/src/libs/repositories/mongo/mongo-generic.repository.ts +++ b/backend/src/libs/repositories/mongo/mongo-generic.repository.ts @@ -1,5 +1,5 @@ import { ClientSession, FilterQuery, Model, QueryOptions, UpdateQuery } from 'mongoose'; -import { BaseInterfaceRepository, PopulateType} from '../interfaces/base.repository.interface'; +import { BaseInterfaceRepository, PopulateType } from '../interfaces/base.repository.interface'; import { ModelProps, SelectedValues } from '../types'; export class MongoGenericRepository implements BaseInterfaceRepository { @@ -61,6 +61,7 @@ export class MongoGenericRepository implements BaseInterfaceRepository { options?: QueryOptions ): Promise { return this._repository.findOneAndUpdate(value, query, options).exec(); + } findOneAndRemove(id: string, withSession = false): Promise { return this._repository @@ -104,6 +105,5 @@ export class MongoGenericRepository implements BaseInterfaceRepository { async endSession() { await this._session.endSession(); - } } From 9a2216a11440ec1d5dcfa2f985dd0de3ff255307 Mon Sep 17 00:00:00 2001 From: Rafael Batista Date: Tue, 13 Dec 2022 11:24:25 +0000 Subject: [PATCH 16/16] refactor: resolved pr requested changes --- .../partials/CardUser/SuperAdmin.tsx | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx index 522a8464e..4066fff2f 100644 --- a/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx +++ b/frontend/src/components/Users/UsersList/partials/CardUser/SuperAdmin.tsx @@ -38,25 +38,14 @@ const SuperAdmin = ({ userSAdmin, loggedUserSAdmin, userId, loggedUserId }: Supe if (loggedUserSAdmin) { return ( - {loggedUserId !== userId && ( - - )} - - {loggedUserId === userId && ( - - )} + ); }