Skip to content

Commit 55564fe

Browse files
committed
d7/interview: Add list of interviews for back navigation
Signed-off-by: SeeuSim <[email protected]>
1 parent dcfcf6b commit 55564fe

File tree

11 files changed

+172
-2
lines changed

11 files changed

+172
-2
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Request, Response } from 'express';
2+
import { StatusCodes } from 'http-status-codes';
3+
4+
import { getRoomsService } from '@/service/get/rooms-get-service';
5+
6+
type QueryParams = {
7+
userId: string;
8+
offset?: number;
9+
limit?: number;
10+
};
11+
12+
export async function getRoomsController(
13+
req: Request<unknown, unknown, unknown, Partial<QueryParams>>,
14+
res: Response
15+
) {
16+
const { userId, ...rest } = req.query;
17+
18+
if (!userId) {
19+
return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json('Malformed Request');
20+
}
21+
22+
const response = await getRoomsService({ userId, ...rest });
23+
24+
if (response.data) {
25+
return res.status(response.code).json(response.data);
26+
}
27+
28+
return res
29+
.status(response.code)
30+
.json({ error: response.error || { message: 'An error occurred' } });
31+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import express from 'express';
22

33
import { getCollabRoom } from '@/controller/collab-controller';
4+
import { getRoomsController } from '@/controller/get-rooms-controller';
45
import { authCheck } from '@/controller/room-auth-controller';
56

67
const router = express.Router();
78

89
router.get('/', getCollabRoom);
10+
router.get('/rooms', getRoomsController);
911
router.get('/auth', authCheck);
1012

1113
export default router;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { desc, eq, type InferSelectModel, or } from 'drizzle-orm';
2+
import { StatusCodes } from 'http-status-codes';
3+
4+
import { db, rooms } from '@/lib/db';
5+
import { logger } from '@/lib/utils';
6+
import type { IServiceResponse } from '@/types';
7+
8+
import type { IGetRoomsPayload } from './types';
9+
10+
export const getRoomsService = async (
11+
params: IGetRoomsPayload
12+
): Promise<IServiceResponse<Array<InferSelectModel<typeof rooms>>>> => {
13+
const { offset, limit: rawLimit, userId } = params;
14+
const limit = rawLimit && rawLimit > 0 ? rawLimit : 10;
15+
let query = db
16+
.select()
17+
.from(rooms)
18+
.where(or(eq(rooms.userId1, userId), eq(rooms.userId2, userId)))
19+
.limit(limit)
20+
.$dynamic();
21+
22+
if (offset) {
23+
query = query.offset(offset * limit);
24+
}
25+
26+
query = query.orderBy(desc(rooms.createdAt));
27+
28+
try {
29+
const result = await query;
30+
return {
31+
code: StatusCodes.OK,
32+
data: result,
33+
};
34+
} catch (error) {
35+
const { name, message, stack, cause } = error as Error;
36+
logger.error(`An error occurred: ` + JSON.stringify({ name, message, stack, cause }));
37+
return {
38+
code: StatusCodes.INTERNAL_SERVER_ERROR,
39+
error: {
40+
message,
41+
},
42+
};
43+
}
44+
};

backend/collaboration/src/service/get/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ export type IGetAuthRoomPayload = {
1414
roomId: string;
1515
userId: string;
1616
};
17+
18+
export type IGetRoomsPayload = {
19+
userId: string;
20+
offset?: number;
21+
limit?: number;
22+
};

frontend/src/components/blocks/nav-bar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ const NavBar = observer(() => {
2626
<Button variant='ghost' asChild>
2727
<Link to={ROUTES.QUESTIONS}>Questions</Link>
2828
</Button>
29+
<Button variant='ghost' asChild>
30+
<Link to={ROUTES.INTERVIEWS}>Interviews</Link>
31+
</Button>
2932
</>
3033
)}
3134
<div className='ml-auto flex items-center gap-4 md:ml-auto md:gap-2 lg:gap-4'>

frontend/src/lib/router.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { RootLayout } from '@/components/blocks/root-layout';
55
import { loader as routeGuardLoader, RouteGuard } from '@/components/blocks/route-guard';
66
import { ForgotPassword } from '@/routes/forgot-password';
77
import { HomePage } from '@/routes/home';
8+
import { InterviewsPage } from '@/routes/interview';
89
import { InterviewRoomContainer, loader as interviewRoomLoader } from '@/routes/interview/[room]';
910
import { Login } from '@/routes/login';
1011
import { loader as topicsLoader } from '@/routes/match/logic';
@@ -44,6 +45,10 @@ export const router = createBrowserRouter([
4445
loader: questionDetailsLoader(queryClient),
4546
element: <QuestionDetailsPage />,
4647
},
48+
{
49+
path: ROUTES.INTERVIEWS,
50+
element: <InterviewsPage />,
51+
},
4752
{
4853
path: ROUTES.INTERVIEW,
4954
loader: interviewRoomLoader,

frontend/src/lib/routes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const ROUTES = {
88
QUESTION_DETAILS: '/questions/:questionId',
99

1010
MATCH: '/match',
11+
INTERVIEWS: '/interviews',
1112
INTERVIEW: '/interview/:roomId',
1213
};
1314

@@ -30,6 +31,12 @@ const TOP_LEVEL_AUTHED_ROUTES = {
3031
title: 'Interview',
3132
},
3233
],
34+
[ROUTES.INTERVIEWS]: [
35+
{
36+
path: ROUTES.INTERVIEWS,
37+
title: 'Interviews',
38+
},
39+
],
3340
};
3441

3542
export type BreadCrumb = {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './main';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useInfiniteQuery } from '@tanstack/react-query';
2+
import { useEffect, useMemo } from 'react';
3+
4+
import { WithNavBanner } from '@/components/blocks/authed';
5+
import { useCrumbs } from '@/lib/hooks';
6+
import { getRooms } from '@/services/collab-service';
7+
import { useAuthedRoute } from '@/stores/auth-store';
8+
import { IInterviewRoom } from '@/types/collab-types';
9+
10+
export const InterviewsPage = () => {
11+
const { userId } = useAuthedRoute();
12+
const { crumbs } = useCrumbs();
13+
const { data, isFetchingNextPage, hasNextPage, fetchNextPage } = useInfiniteQuery<
14+
Array<IInterviewRoom>
15+
>({
16+
queryKey: ['interviews', userId],
17+
initialPageParam: 0,
18+
queryFn: async ({ pageParam }) => getRooms(userId, pageParam as number),
19+
getNextPageParam: (_lastPage, pages) => {
20+
if (_lastPage.length === 0) {
21+
return undefined;
22+
}
23+
24+
return pages.length;
25+
},
26+
});
27+
28+
useEffect(() => {
29+
if (!isFetchingNextPage && hasNextPage) {
30+
fetchNextPage();
31+
}
32+
}, [fetchNextPage, isFetchingNextPage, hasNextPage]);
33+
34+
const _rooms = useMemo(() => {
35+
return data ? data.pages.flatMap((page) => page) : [];
36+
}, [data]);
37+
38+
return (
39+
<WithNavBanner crumbs={crumbs}>
40+
<div />
41+
</WithNavBanner>
42+
);
43+
};

frontend/src/services/collab-service.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { IInterviewRoom } from '@/types/collab-types';
2+
13
import { collabApiGetClient } from './api-clients';
24

35
const COLLAB_SERVICE_ROUTES = {
46
CHECK_ROOM_AUTH: '/room/auth',
7+
GET_ROOMS: '/room/rooms',
58
};
69

710
export const checkRoomAuthorization = (roomId: string, userId: string) => {
@@ -13,8 +16,25 @@ export const checkRoomAuthorization = (roomId: string, userId: string) => {
1316
.then((response) => {
1417
return { isAuthed: response.status < 400, questionId: response.data?.questionId };
1518
})
16-
.catch((err) => {
17-
console.log(err);
19+
.catch((_err) => {
1820
return { isAuthed: false, questionId: undefined };
1921
});
2022
};
23+
24+
export const getRooms = (userId: string, offset?: number, limit?: number) => {
25+
const searchParams = new URLSearchParams();
26+
searchParams.set('userId', userId);
27+
28+
if (offset) {
29+
searchParams.set('offset', String(offset));
30+
}
31+
32+
if (limit) {
33+
searchParams.set('limit', String(limit));
34+
}
35+
36+
return collabApiGetClient
37+
.get(`${COLLAB_SERVICE_ROUTES.GET_ROOMS}?${searchParams.toString()}`)
38+
.then((response) => response.data as Array<IInterviewRoom>)
39+
.catch((_err) => []);
40+
};

0 commit comments

Comments
 (0)