Skip to content

Commit 8088237

Browse files
committed
Add edit question
1 parent 5dad4c4 commit 8088237

File tree

10 files changed

+308
-157
lines changed

10 files changed

+308
-157
lines changed

backend/transport/replace_question.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,21 @@ func ReplaceQuestionWithLogger(db *database.QuestionDB, logger *common.Logger) g
3333
return
3434
}
3535

36-
if id_param >= db.FindNextQuestionId() {
37-
logger.Log.Info(
38-
"Attempting to update a question with an ID greater than next ID, creating a new question",
39-
)
40-
status, err := db.AddQuestion(logger, &new_question)
41-
42-
if err != nil {
43-
ctx.JSON(status, err.Error())
44-
return
45-
}
46-
47-
ctx.JSON(status, "Question added successfully")
48-
logger.Log.Info("Question added successfully")
49-
return
50-
}
36+
//if id_param >= db.FindNextQuestionId() {
37+
// logger.Log.Info(
38+
// "Attempting to update a question with an ID greater than next ID, creating a new question",
39+
// )
40+
// status, err := db.AddQuestion(logger, &new_question)
41+
//
42+
// if err != nil {
43+
// ctx.JSON(status, err.Error())
44+
// return
45+
// }
46+
//
47+
// ctx.JSON(status, "Question added successfully")
48+
// logger.Log.Info("Question added successfully")
49+
// return
50+
//}
5151

5252
logger.Log.Info("Replacing question with ID: ", id_param)
5353
new_question.Id = id_param

peerprep/api/gateway.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { cookies } from "next/headers";
2-
import { LoginResponse, Question, UserServiceResponse, StatusBody } from "./structs";
2+
import {
3+
LoginResponse,
4+
Question,
5+
StatusBody,
6+
UserServiceResponse,
7+
} from "./structs";
38
import DOMPurify from "isomorphic-dompurify";
49

510
export function generateAuthHeaders() {
611
return {
7-
"Authorization": `Bearer ${cookies().get("session")?.value}`,
8-
};;
12+
Authorization: `Bearer ${cookies().get("session")?.value}`,
13+
};
914
}
1015

1116
export function generateJSONHeaders() {
@@ -24,6 +29,7 @@ export async function fetchQuestion(
2429
{
2530
method: "GET",
2631
headers: generateAuthHeaders(),
32+
next: { revalidate: 5 },
2733
},
2834
);
2935
if (!response.ok) {
@@ -105,7 +111,7 @@ export async function verifyUser(): Promise<UserServiceResponse | StatusBody> {
105111
{
106112
method: "GET",
107113
headers: generateAuthHeaders(),
108-
}
114+
},
109115
);
110116
const json = await res.json();
111117

peerprep/api/structs.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { z } from "zod";
1+
import { z, ZodType } from "zod";
22

33
export enum Difficulty {
44
All = "All",
@@ -114,3 +114,16 @@ export const LoginFormSchema = z.object({
114114
export function isError(obj: any | StatusBody): obj is StatusBody {
115115
return (obj as StatusBody).status !== undefined;
116116
}
117+
118+
export const QuestionSchema = z.object({
119+
difficulty: z.nativeEnum(Difficulty),
120+
title: z.string().min(2, {
121+
message: "Please input a title.",
122+
}),
123+
content: z.string().min(2, {
124+
message: "Please input content.",
125+
}),
126+
topicTags: z.array(z.string()).min(1, {
127+
message: "Please input at least one topic tag.",
128+
}),
129+
}) satisfies ZodType<QuestionFullBody>;

peerprep/app/api/internal/questions/helper.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { QuestionFullBody, StatusBody } from "@/api/structs";
1+
import { Question, QuestionFullBody, StatusBody } from "@/api/structs";
22

33
export async function deleteQuestion(id: number): Promise<StatusBody> {
44
const res = await fetch(
@@ -15,11 +15,24 @@ export async function deleteQuestion(id: number): Promise<StatusBody> {
1515
return json as StatusBody;
1616
}
1717

18+
export async function editQuestion(question: Question): Promise<StatusBody> {
19+
const res = await fetch(
20+
`${process.env.NEXT_PUBLIC_NGINX}/api/internal/questions`,
21+
{
22+
method: "PUT",
23+
body: JSON.stringify(question),
24+
},
25+
);
26+
if (!res.ok) {
27+
return { status: res.status };
28+
}
29+
const json = await res.json();
30+
return json as StatusBody;
31+
}
32+
1833
export async function addQuestion(
1934
question: QuestionFullBody,
2035
): Promise<StatusBody> {
21-
// TODO: this is not desired
22-
question.content = "<p>" + question.content + "</p>";
2336
const res = await fetch(
2437
`${process.env.NEXT_PUBLIC_NGINX}/api/internal/questions`,
2538
{

peerprep/app/api/internal/questions/route.ts

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { generateAuthHeaders, generateJSONHeaders } from "@/api/gateway";
22
import { NextRequest, NextResponse } from "next/server";
3+
import { Question } from "@/api/structs";
34

45
export async function GET() {
56
try {
@@ -8,22 +9,54 @@ export async function GET() {
89
{
910
method: "GET",
1011
headers: generateAuthHeaders(),
11-
}
12+
},
1213
);
1314
if (!response.ok) {
1415
return NextResponse.json(
1516
{
1617
error: await response.text(),
1718
status: response.status,
1819
},
19-
{ status: response.status }
20+
{ status: response.status },
2021
);
2122
}
2223
return response;
2324
} catch (err: any) {
2425
return NextResponse.json(
2526
{ error: err.message, status: 400 },
26-
{ status: 400 }
27+
{ status: 400 },
28+
);
29+
}
30+
}
31+
32+
export async function PUT(request: NextRequest) {
33+
const body = (await request.json()) as Question;
34+
try {
35+
const response = await fetch(
36+
`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions/replace/${body.id}`,
37+
{
38+
method: "PUT",
39+
body: JSON.stringify(body),
40+
headers: generateJSONHeaders(),
41+
},
42+
);
43+
if (response.ok) {
44+
return NextResponse.json(
45+
{ status: response.status },
46+
{ status: response.status },
47+
);
48+
}
49+
return NextResponse.json(
50+
{
51+
error: (await response.json())["Error adding question: "],
52+
status: response.status,
53+
},
54+
{ status: response.status },
55+
);
56+
} catch (err: any) {
57+
return NextResponse.json(
58+
{ error: err.message, status: 400 },
59+
{ status: 400 },
2760
);
2861
}
2962
}
@@ -37,25 +70,25 @@ export async function POST(request: NextRequest) {
3770
method: "POST",
3871
body: JSON.stringify(body),
3972
headers: generateJSONHeaders(),
40-
}
73+
},
4174
);
4275
if (response.ok) {
4376
return NextResponse.json(
4477
{ status: response.status },
45-
{ status: response.status }
78+
{ status: response.status },
4679
);
4780
}
4881
return NextResponse.json(
4982
{
5083
error: (await response.json())["Error adding question: "],
5184
status: response.status,
5285
},
53-
{ status: response.status }
86+
{ status: response.status },
5487
);
5588
} catch (err: any) {
5689
return NextResponse.json(
5790
{ error: err.message, status: 400 },
58-
{ status: 400 }
91+
{ status: 400 },
5992
);
6093
}
6194
}
@@ -65,7 +98,7 @@ export async function DELETE(request: NextRequest) {
6598
if (body.qid === undefined) {
6699
return NextResponse.json(
67100
{ error: "No ID specified.", status: 400 },
68-
{ status: 400 }
101+
{ status: 400 },
69102
);
70103
}
71104
try {
@@ -74,7 +107,7 @@ export async function DELETE(request: NextRequest) {
74107
{
75108
method: "DELETE",
76109
headers: generateAuthHeaders(),
77-
}
110+
},
78111
);
79112
if (response.ok) {
80113
// NextResponse doesn't support 204.
@@ -85,12 +118,12 @@ export async function DELETE(request: NextRequest) {
85118
error: (await response.json())["Error deleting question: "],
86119
status: response.status,
87120
},
88-
{ status: response.status }
121+
{ status: response.status },
89122
);
90123
} catch (err: any) {
91124
return NextResponse.json(
92125
{ error: `Bad request: ${err.message}`, status: 400 },
93-
{ status: 400 }
126+
{ status: 400 },
94127
);
95128
}
96129
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"use client";
2+
3+
import {
4+
Form,
5+
FormControl,
6+
FormField,
7+
FormItem,
8+
FormLabel,
9+
FormMessage,
10+
} from "@/components/ui/form";
11+
import { Input } from "@/components/ui/input";
12+
import {
13+
Select,
14+
SelectContent,
15+
SelectItem,
16+
SelectTrigger,
17+
SelectValue,
18+
} from "@/components/ui/select";
19+
import { Difficulty, QuestionSchema } from "@/api/structs";
20+
import { Controller, UseFormReturn } from "react-hook-form";
21+
import { InputTags } from "@/components/ui/tags";
22+
import Tiptap from "@/components/modifyQuestion/Tiptap";
23+
import { Button } from "@/components/ui/button";
24+
import { z } from "zod";
25+
26+
type FormType = UseFormReturn<
27+
{
28+
difficulty: Difficulty;
29+
title: string;
30+
content: string;
31+
topicTags: string[];
32+
},
33+
any,
34+
undefined
35+
>;
36+
37+
type QuestionFormProps = {
38+
form: FormType;
39+
onSubmit: (values: z.infer<typeof QuestionSchema>) => Promise<void>;
40+
};
41+
42+
const QuestionForm = ({ form, onSubmit }: QuestionFormProps) => {
43+
return (
44+
<Form {...form}>
45+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
46+
<FormField
47+
control={form.control}
48+
name="title"
49+
render={({ field }) => (
50+
<FormItem>
51+
<FormLabel>Title</FormLabel>
52+
<FormControl>
53+
<Input placeholder="Two Sum" {...field} />
54+
</FormControl>
55+
<FormMessage />
56+
</FormItem>
57+
)}
58+
/>
59+
<FormField
60+
control={form.control}
61+
name="difficulty"
62+
render={({ field }) => (
63+
<FormItem>
64+
<FormLabel>Difficulty</FormLabel>
65+
<Select onValueChange={field.onChange} defaultValue={field.value}>
66+
<FormControl>
67+
<SelectTrigger>
68+
<SelectValue placeholder="Select a difficulty" />
69+
</SelectTrigger>
70+
</FormControl>
71+
<SelectContent>
72+
<SelectItem value={Difficulty.Easy}>Easy</SelectItem>
73+
<SelectItem value={Difficulty.Medium}>Medium</SelectItem>
74+
<SelectItem value={Difficulty.Hard}>Hard</SelectItem>
75+
</SelectContent>
76+
</Select>
77+
<FormMessage />
78+
</FormItem>
79+
)}
80+
/>
81+
<FormField
82+
control={form.control}
83+
name="topicTags"
84+
render={({ field }) => (
85+
<FormItem className="flex w-full flex-col items-start">
86+
<FormLabel className="text-left">Topics</FormLabel>
87+
<FormControl className="w-full">
88+
<Controller
89+
name="topicTags"
90+
control={form.control}
91+
render={({ field }) => (
92+
<InputTags
93+
value={field.value}
94+
onChange={field.onChange}
95+
placeholder="Press 'Enter' to add topics. "
96+
/>
97+
)}
98+
/>
99+
</FormControl>
100+
<FormMessage />
101+
</FormItem>
102+
)}
103+
/>
104+
<FormField
105+
control={form.control}
106+
name="content"
107+
render={({ field }) => (
108+
<FormItem>
109+
<FormLabel>Content</FormLabel>
110+
<FormControl>
111+
<Tiptap
112+
defaultContent={field.value}
113+
onChange={field.onChange}
114+
/>
115+
</FormControl>
116+
<FormMessage />
117+
</FormItem>
118+
)}
119+
/>
120+
<Button type="submit">Submit</Button>
121+
</form>
122+
</Form>
123+
);
124+
};
125+
126+
export default QuestionForm;

0 commit comments

Comments
 (0)