Skip to content

Commit 9eb79db

Browse files
committed
Set edit/add question to admin only actions
1 parent 81ec1b0 commit 9eb79db

File tree

15 files changed

+172
-131
lines changed

15 files changed

+172
-131
lines changed

comms/package-lock.json

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

peerprep/api/gateway.ts

Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import { cookies } from "next/headers";
2-
import {
3-
LoginResponse,
4-
Question,
5-
StatusBody,
6-
UserServiceResponse,
7-
} from "./structs";
8-
import DOMPurify from "isomorphic-dompurify";
2+
import { LoginResponse, StatusBody, UserServiceResponse } from "./structs";
93
import { CookieNames } from "@/app/actions/session";
10-
import { revalidatePath } from "next/cache";
114

125
export function generateAuthHeaders() {
136
return {
@@ -30,49 +23,20 @@ export function generateJSONHeaders() {
3023
};
3124
}
3225

33-
export async function fetchQuestion(
34-
questionId: number,
35-
): Promise<Question | StatusBody> {
36-
try {
37-
const response = await fetch(
38-
`${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions/solve/${questionId}`,
39-
{
40-
method: "GET",
41-
headers: generateAuthHeaders(),
42-
cache: "no-store",
43-
},
44-
);
45-
if (!response.ok) {
46-
return {
47-
error: await response.text(),
48-
status: response.status,
49-
};
50-
}
51-
52-
const question = (await response.json()) as Question;
53-
question.content = DOMPurify.sanitize(question.content);
54-
revalidatePath(`/questions/edit/${questionId}`);
55-
return question;
56-
} catch (err: any) {
57-
return { error: err.message, status: 400 };
58-
}
59-
}
26+
export const userServiceUrl = `${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_USER_SERVICE}`;
6027

6128
export async function getSessionLogin(validatedFields: {
6229
email: string;
6330
password: string;
6431
}): Promise<LoginResponse | StatusBody> {
6532
try {
66-
const res = await fetch(
67-
`${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_USER_SERVICE}/auth/login`,
68-
{
69-
method: "POST",
70-
body: JSON.stringify(validatedFields),
71-
headers: {
72-
"Content-type": "application/json; charset=UTF-8",
73-
},
33+
const res = await fetch(`${userServiceUrl}/auth/login`, {
34+
method: "POST",
35+
body: JSON.stringify(validatedFields),
36+
headers: {
37+
"Content-type": "application/json; charset=UTF-8",
7438
},
75-
);
39+
});
7640
const json = await res.json();
7741

7842
if (!res.ok) {
@@ -93,16 +57,13 @@ export async function postSignupUser(validatedFields: {
9357
}): Promise<UserServiceResponse | StatusBody> {
9458
try {
9559
console.log(JSON.stringify(validatedFields));
96-
const res = await fetch(
97-
`${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_USER_SERVICE}/users`,
98-
{
99-
method: "POST",
100-
body: JSON.stringify(validatedFields),
101-
headers: {
102-
"Content-type": "application/json; charset=UTF-8",
103-
},
60+
const res = await fetch(`${userServiceUrl}/users`, {
61+
method: "POST",
62+
body: JSON.stringify(validatedFields),
63+
headers: {
64+
"Content-type": "application/json; charset=UTF-8",
10465
},
105-
);
66+
});
10667
const json = await res.json();
10768

10869
if (!res.ok) {
@@ -118,14 +79,11 @@ export async function postSignupUser(validatedFields: {
11879

11980
export async function verifyUser(): Promise<UserServiceResponse | StatusBody> {
12081
try {
121-
const res = await fetch(
122-
`${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_USER_SERVICE}/auth/verify-token`,
123-
{
124-
method: "GET",
125-
headers: generateAuthHeaders(),
126-
},
127-
);
128-
const json = await res.json();
82+
const res = await fetch(`${userServiceUrl}/auth/verify-token`, {
83+
method: "GET",
84+
headers: generateAuthHeaders(),
85+
});
86+
const json = (await res.json()) as UserServiceResponse;
12987

13088
if (!res.ok) {
13189
return { error: json.message, status: res.status };

peerprep/app/actions/server_actions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
isError,
77
LoginFormSchema,
88
SignupFormSchema,
9+
UserData,
910
UserServiceResponse,
1011
} from "@/api/structs";
1112
import { createSession } from "@/app/actions/session";
@@ -62,7 +63,7 @@ export async function login(state: FormState, formData: FormData) {
6263
}
6364
}
6465

65-
export async function hydrateUid(): Promise<undefined | string> {
66+
export async function hydrateUid(): Promise<null | UserData> {
6667
if (!cookies().has("session")) {
6768
// TODO: this should not be required because of middleware
6869
console.log("No session found - triggering switch back to login page.");
@@ -74,8 +75,7 @@ export async function hydrateUid(): Promise<undefined | string> {
7475
console.log(`Error ${json.status}: ${json.error}`);
7576
redirect("/api/internal/auth/expire");
7677
}
77-
7878
// TODO: handle error handling
7979
const response = json as UserServiceResponse;
80-
return response.data.id;
80+
return response.data;
8181
}

peerprep/app/questions/[question]/[roomID]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { fetchQuestion, getSessionToken, getUserData } from "@/api/gateway";
1+
import { getSessionToken, getUserData } from "@/api/gateway";
22
import { isError, Question as QnType, StatusBody } from "@/api/structs";
33
import styles from "@/style/question.module.css";
44
import ErrorBlock from "@/components/shared/ErrorBlock";
55
import React from "react";
66
import QuestionBlock from "./question";
7+
import { fetchQuestion } from "@/app/questions/helper";
78

89
type Props = {
910
searchParams: {

peerprep/app/questions/[question]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { fetchQuestion } from "@/api/gateway";
21
import { isError, Question as QnType, StatusBody } from "@/api/structs";
32
import styles from "@/style/question.module.css";
43
import ErrorBlock from "@/components/shared/ErrorBlock";
54
import React from "react";
65
import QuestionBlock from "./question";
76

7+
import { fetchQuestion } from "@/app/questions/helper";
8+
89
type Props = {
910
params: {
1011
question: number;

peerprep/app/questions/edit/[question]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { fetchQuestion } from "@/api/gateway";
21
import React from "react";
32
import EditQuestion from "@/app/questions/edit/[question]/EditQuestion";
43
import { Question } from "@/api/structs";
54
import { revalidatePath } from "next/cache";
65

6+
import { fetchQuestion } from "@/app/questions/helper";
7+
78
type Props = {
89
params: {
910
question: number;

peerprep/app/questions/helper.ts

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
"use server";
22

3-
import { Question, QuestionFullBody, StatusBody } from "@/api/structs";
3+
import { isError, Question, QuestionFullBody, StatusBody } from "@/api/structs";
44
import { revalidatePath } from "next/cache";
5-
import { generateAuthHeaders, generateJSONHeaders } from "@/api/gateway";
5+
import {
6+
generateAuthHeaders,
7+
generateJSONHeaders,
8+
verifyUser,
9+
} from "@/api/gateway";
10+
import DOMPurify from "isomorphic-dompurify";
11+
12+
const questionServiceUrl = `${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_QUESTION_SERVICE}`;
613

714
export async function deleteQuestion(id: number): Promise<StatusBody> {
8-
const res = await fetch(
9-
`${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions/delete/${id}`,
10-
{
11-
method: "DELETE",
12-
headers: generateAuthHeaders(),
13-
},
14-
);
15+
const verify = await verifyUser();
16+
if (isError(verify) || verify?.data.isAdmin === false) {
17+
return verify as StatusBody;
18+
}
19+
20+
const res = await fetch(`${questionServiceUrl}/questions/delete/${id}`, {
21+
method: "DELETE",
22+
headers: generateAuthHeaders(),
23+
});
1524
if (res.ok) {
1625
return { status: res.status };
1726
}
@@ -20,10 +29,29 @@ export async function deleteQuestion(id: number): Promise<StatusBody> {
2029
return json as StatusBody;
2130
}
2231

32+
export async function fetchAllQuestions(): Promise<StatusBody | Question[]> {
33+
console.log("Fetching all questions...");
34+
const res = await fetch(`${questionServiceUrl}/questions`, {
35+
method: "GET",
36+
headers: generateAuthHeaders(),
37+
cache: "no-store",
38+
});
39+
if (!res.ok) {
40+
return { status: res.status };
41+
}
42+
const json = await res.json();
43+
return json as Question[];
44+
}
45+
2346
export async function editQuestion(question: Question): Promise<StatusBody> {
47+
const verify = await verifyUser();
48+
if (isError(verify) || verify?.data.isAdmin === false) {
49+
return verify as StatusBody;
50+
}
51+
2452
console.log("editing question", question.id);
2553
const res = await fetch(
26-
`${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions/replace/${question.id}`,
54+
`${questionServiceUrl}/questions/replace/${question.id}`,
2755
{
2856
method: "PUT",
2957
body: JSON.stringify(question),
@@ -42,10 +70,12 @@ export async function editQuestion(question: Question): Promise<StatusBody> {
4270
export async function addQuestion(
4371
question: QuestionFullBody,
4472
): Promise<StatusBody> {
73+
const verify = await verifyUser();
74+
if (isError(verify) || verify?.data.isAdmin === false) {
75+
return verify as StatusBody;
76+
}
4577
console.log("Adding question", question.title);
46-
const url = `${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions`;
47-
console.log(url);
48-
const res = await fetch(url, {
78+
const res = await fetch(`${questionServiceUrl}/questions`, {
4979
method: "POST",
5080
body: JSON.stringify(question),
5181
headers: generateJSONHeaders(),
@@ -57,3 +87,31 @@ export async function addQuestion(
5787
const json = await res.json();
5888
return json as StatusBody;
5989
}
90+
91+
export async function fetchQuestion(
92+
questionId: number,
93+
): Promise<Question | StatusBody> {
94+
try {
95+
const response = await fetch(
96+
`${questionServiceUrl}/questions/solve/${questionId}`,
97+
{
98+
method: "GET",
99+
headers: generateAuthHeaders(),
100+
cache: "no-store",
101+
},
102+
);
103+
if (!response.ok) {
104+
return {
105+
error: await response.text(),
106+
status: response.status,
107+
};
108+
}
109+
110+
const question = (await response.json()) as Question;
111+
question.content = DOMPurify.sanitize(question.content);
112+
revalidatePath(`/questions/edit/${questionId}`);
113+
return question;
114+
} catch (err: any) {
115+
return { error: err.message, status: 400 };
116+
}
117+
}

peerprep/app/questions/loading.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const LoadingPage = () => {
22
return (
3-
<div>
4-
<h1>Loading...</h1>
3+
<div className="flex h-screen items-center justify-center">
4+
<div className="text-2xl">Loading...</div>
5+
<div className="text-2xl">Please wait...</div>
56
</div>
67
);
78
};
File renamed without changes.

peerprep/app/questions/page.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,32 @@ import QuestionList from "@/components/questionpage/QuestionList";
33
import Matchmaking from "@/components/questionpage/Matchmaking";
44
import { QuestionFilterProvider } from "@/contexts/QuestionFilterContext";
55
import { hydrateUid } from "../actions/server_actions";
6-
import { Question } from "@/api/structs";
7-
import { generateAuthHeaders } from "@/api/gateway";
6+
import { isError, Question, StatusBody, UserData } from "@/api/structs";
87
import { UserInfoProvider } from "@/contexts/UserInfoContext";
8+
import { fetchAllQuestions } from "@/app/questions/helper";
99

1010
async function QuestionsPage() {
11-
const userId = await hydrateUid();
11+
const userData = (await hydrateUid()) as UserData;
1212

13-
const questions: Question[] = await fetch(
14-
`${process.env.NEXT_PUBLIC_NGINX}/${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions`,
15-
{
16-
method: "GET",
17-
headers: generateAuthHeaders(),
18-
cache: "no-store",
19-
},
20-
).then((res) => res.json());
13+
const questions: Question[] | StatusBody = await fetchAllQuestions();
14+
15+
if (isError(questions)) {
16+
return (
17+
<div className="flex h-screen items-center justify-center">
18+
<div className="text-2xl">Error...</div>
19+
</div>
20+
);
21+
}
2122

2223
return (
23-
<UserInfoProvider userid={userId}>
24+
<UserInfoProvider userData={userData}>
2425
<QuestionFilterProvider>
2526
<div className="flex h-screen flex-col overflow-hidden">
2627
<div className="sticky top-0">
2728
<Matchmaking />
2829
</div>
2930
<div className="flex-grow overflow-y-auto">
30-
<QuestionList questions={questions} />
31+
<QuestionList questions={questions as unknown as Question[]} />
3132
</div>
3233
</div>
3334
</QuestionFilterProvider>

0 commit comments

Comments
 (0)