Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE]: get all users #610

Merged
merged 3 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ export class GetUserApplicationImpl implements GetUserApplication {
countUsers() {
return this.getUserService.countUsers();
}

getAllUsers() {
return this.getUserService.getAllUsers();
}
}
49 changes: 49 additions & 0 deletions backend/src/modules/users/controller/users.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiBearerAuth,
ApiInternalServerErrorResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse
} from '@nestjs/swagger';

import JwtAuthenticationGuard from 'libs/guards/jwtAuth.guard';
import { BadRequestResponse } from 'libs/swagger/errors/bad-request.swagger';
import { InternalServerErrorResponse } from 'libs/swagger/errors/internal-server-error.swagger';
import { UnauthorizedResponse } from 'libs/swagger/errors/unauthorized.swagger';

import UserDto from '../dto/user.dto';
import { GetUserApplication } from '../interfaces/applications/get.user.application.interface';
import { TYPES } from '../interfaces/types';

@ApiBearerAuth('access-token')
@ApiTags('Users')
@UseGuards(JwtAuthenticationGuard)
@Controller('users')
export default class UsersController {
constructor(
@Inject(TYPES.applications.GetUserApplication)
private getUserApp: GetUserApplication
) {}

@ApiOperation({ summary: 'Retrieve a list of existing users' })
@ApiOkResponse({ description: 'Users successfully retrieved!', type: UserDto, isArray: true })
@ApiUnauthorizedResponse({
description: 'Unauthorized',
type: UnauthorizedResponse
})
@ApiBadRequestResponse({
description: 'Bad Request',
type: BadRequestResponse
})
@ApiInternalServerErrorResponse({
description: 'Internal Server Error',
type: InternalServerErrorResponse
})
@Get()
getAllUsers() {
return this.getUserApp.getAllUsers();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ export interface GetUserApplication {
getByEmail(email: string): Promise<LeanDocument<UserDocument> | null>;

countUsers(): Promise<number>;

getAllUsers(): Promise<LeanDocument<UserDocument>[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export interface GetUserService {
): Promise<LeanDocument<UserDocument> | false>;

countUsers(): Promise<number>;

getAllUsers(): Promise<LeanDocument<UserDocument>[]>;
}
4 changes: 4 additions & 0 deletions backend/src/modules/users/services/get.user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ export default class GetUserServiceImpl implements GetUserService {
countUsers() {
return this.userModel.countDocuments().exec();
}

getAllUsers() {
return this.userModel.find().select('-password -currentHashedRefreshToken').lean().exec();
}
}
2 changes: 2 additions & 0 deletions backend/src/modules/users/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
import { mongooseResetModule, mongooseUserModule } from 'infrastructure/database/mongoose.module';
import TeamsModule from 'modules/teams/teams.module';

import UsersController from './controller/users.controller';
import {
createUserService,
getUserApplication,
Expand All @@ -20,6 +21,7 @@ import {
updateUserApplication,
getUserApplication
],
controllers: [UsersController],
exports: [
createUserService,
getUserService,
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/api/userService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GetServerSidePropsContext } from 'next';

import fetchData from 'utils/fetchData';
import { User } from '../types/user/user';

export const getAllUsers = (context?: GetServerSidePropsContext): Promise<User[]> => {
return fetchData(`/users`, { context, serverSide: !!context });
};
12 changes: 6 additions & 6 deletions frontend/src/components/Sidebar/partials/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,18 @@ const SideBarContent: React.FC<SidebarContentProps> = ({ strategy }) => {
<StyledText>Boards</StyledText>
</StyledMenuItem>
</Link>
<Tooltip color="primary100" content="Coming Soon">
<StyledMenuItem disabled align="center" data-active={active === USERS_ROUTE}>
<Link href={USERS_ROUTE}>
<StyledMenuItem align="center" data-active={active === USERS_ROUTE}>
<Icon name="user" />
<StyledText>Users</StyledText>
</StyledMenuItem>
</Tooltip>
<Tooltip color="primary100" content="Coming Soon">
<StyledMenuItem disabled align="center" data-active={active === TEAMS_ROUTE}>
</Link>
<Link href={TEAMS_ROUTE}>
<StyledMenuItem align="center" data-active={active === TEAMS_ROUTE}>
<Icon name="team" />
<StyledText>Teams</StyledText>
</StyledMenuItem>
</Tooltip>
</Link>
<StyledSeparator />
<Tooltip color="primary100" content="Coming Soon">
<StyledMenuItem disabled align="center" data-active={active === ACCOUNT_ROUTE}>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/layouts/DashboardLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ type DashboardLayoutProps = {
isBoards: boolean;
isTeams: boolean;
canAddBoard: boolean;
isUsers: boolean;
};

const DashboardLayout = (props: DashboardLayoutProps) => {
const { children, firstName, isDashboard, isBoards, isTeams, canAddBoard } = props;
const { children, firstName, isDashboard, isBoards, isTeams, canAddBoard, isUsers } = props;

return (
<ContentSection gap="36" justify="between">
Expand All @@ -25,6 +26,7 @@ const DashboardLayout = (props: DashboardLayoutProps) => {
{isDashboard && <Text heading="1">Welcome, {firstName}</Text>}
{isBoards && <Text heading="1">Boards</Text>}
{isTeams && <Text heading="1">Teams</Text>}
{isUsers && <Text heading="1">Users</Text>}
{(isDashboard || isBoards) && (
<Link href="/boards/new">
<AddNewBoardButton size={isDashboard ? 'sm' : 'md'}>
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/layouts/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { signOut, useSession } from 'next-auth/react';
import LoadingPage from 'components/loadings/LoadingPage';
import { Sidebar } from 'components/Sidebar';
import { REFRESH_TOKEN_ERROR } from 'utils/constants';
import { BOARDS_ROUTE, DASHBOARD_ROUTE, TEAMS_ROUTE } from 'utils/routes';
import { BOARDS_ROUTE, DASHBOARD_ROUTE, TEAMS_ROUTE, USERS_ROUTE } from 'utils/routes';
import DashboardLayout from '../DashboardLayout';
import { Container } from './styles';

Expand All @@ -20,6 +20,7 @@ const Layout: React.FC<{ children: ReactNode; canAddBoard?: boolean }> = ({
const isDashboard = router.pathname === DASHBOARD_ROUTE;
const isBoards = router.pathname === BOARDS_ROUTE;
const isTeams = router.pathname === TEAMS_ROUTE;
const isUsers = router.pathname === USERS_ROUTE;

if (session?.error === REFRESH_TOKEN_ERROR) {
signOut({ callbackUrl: '/' });
Expand All @@ -34,11 +35,12 @@ const Layout: React.FC<{ children: ReactNode; canAddBoard?: boolean }> = ({
isBoards={isBoards}
isDashboard={isDashboard}
isTeams={isTeams}
isUsers={isUsers}
>
{children}
</DashboardLayout>
);
}, [children, isBoards, isDashboard, session, isTeams, canAddBoard]);
}, [children, isBoards, isDashboard, session, isTeams, canAddBoard, isUsers]);

if (!session) return <LoadingPage />;

Expand Down
62 changes: 62 additions & 0 deletions frontend/src/pages/users/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ReactElement, Suspense } from 'react';
import { dehydrate, QueryClient, useQuery } from 'react-query';
import { GetServerSideProps, GetServerSidePropsContext } from 'next';
import { useSession } from 'next-auth/react';
import { useSetRecoilState } from 'recoil';

import QueryError from 'components/Errors/QueryError';
import Layout from 'components/layouts/Layout';
import LoadingPage from 'components/loadings/LoadingPage';
import Flex from 'components/Primitives/Flex';
import { getAllUsers } from '../../api/userService';
import requireAuthentication from '../../components/HOC/requireAuthentication';
import { toastState } from '../../store/toast/atom/toast.atom';
import { ToastStateEnum } from '../../utils/enums/toast-types';

const Users = () => {
const { data: session } = useSession({ required: true });
const setToastState = useSetRecoilState(toastState);

const { data } = useQuery(['users'], () => getAllUsers(), {
enabled: true,
refetchOnWindowFocus: false,
onError: () => {
setToastState({
open: true,
content: 'Error getting the users',
type: ToastStateEnum.ERROR
});
}
});

if (!session || !data) return null;

return (
<Flex direction="column">
<Suspense fallback={<LoadingPage />}>
<QueryError>
{data.map((user) => (
<h2>{user.email}</h2>
))}
</QueryError>
</Suspense>
</Flex>
);
};

Users.getLayout = (page: ReactElement) => <Layout>{page}</Layout>;

export default Users;

export const getServerSideProps: GetServerSideProps = requireAuthentication(
async (context: GetServerSidePropsContext) => {
const queryClient = new QueryClient();
await queryClient.prefetchQuery('users', () => getAllUsers(context));

return {
props: {
dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient)))
}
};
}
);
3 changes: 2 additions & 1 deletion frontend/src/utils/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export const ROUTES = {
BoardPage: (boardId: string): string => `/boards/${boardId}`,
Token: RESET_PASSWORD_ROUTE,
TokenPage: (tokenId: string): string => `/reset-password/${tokenId}`,
Teams: TEAMS_ROUTE
Teams: TEAMS_ROUTE,
Users: USERS_ROUTE
};

export const GetPageTitleByUrl = (url: string): string | undefined => {
Expand Down