Skip to content

Commit 7431eab

Browse files
authored
Merge pull request #32 from CS3219-AY2425S1/anun/crud
2 parents 8557eee + a8c6756 commit 7431eab

File tree

5 files changed

+199
-18
lines changed

5 files changed

+199
-18
lines changed

backend/question/src/controller/question-controller.ts

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,31 @@ import {
77
getRandomQuestionService,
88
searchQuestionsByTitleService,
99
} from '@/services/get/index';
10+
import type {
11+
ICreateQuestionPayload,
12+
IDeleteQuestionPayload,
13+
IUpdateQuestionPayload,
14+
} from '@/services/post/types';
15+
16+
import {
17+
createQuestionService,
18+
deleteQuestionService,
19+
updateQuestionService,
20+
} from '@/services/post';
1021
import type {
1122
IGetQuestionsPayload,
1223
IGetQuestionPayload,
1324
IGetRandomQuestionPayload,
1425
} from '@/services/get/types';
1526

1627
export const getQuestions = async (req: Request, res: Response): Promise<Response> => {
28+
const { questionName, difficulty, topic, pageNum, recordsPerPage } = req.query;
1729
const payload: IGetQuestionsPayload = {
18-
questionName: req.query.questionName as string,
19-
difficulty: req.query.difficulty as string,
20-
topic: req.query.topic as string[],
21-
pageNum: parseInt(req.query.pageNum as string) || 0,
22-
recordsPerPage: parseInt(req.query.recordsPerPage as string) || 20,
30+
questionName: questionName as string,
31+
difficulty: difficulty as string,
32+
topic: topic as string[],
33+
pageNum: parseInt(pageNum as string) || 0,
34+
recordsPerPage: parseInt(recordsPerPage as string) || 20,
2335
};
2436

2537
try {
@@ -67,6 +79,8 @@ export const getRandomQuestion = async (req: Request, res: Response): Promise<Re
6779
const result = await getRandomQuestionService(payload);
6880
return res.status(result.code).json(result);
6981
} catch (error) {
82+
console.log('error', error);
83+
7084
return res
7185
.status(StatusCodes.INTERNAL_SERVER_ERROR)
7286
.json({ success: false, message: 'An error occurred', error });
@@ -93,3 +107,71 @@ export const searchQuestionsByTitle = async (req: Request, res: Response): Promi
93107
.json({ success: false, message: 'An error occurred', error });
94108
}
95109
};
110+
111+
export const createQuestion = async (req: Request, res: Response): Promise<Response> => {
112+
const { title, description, difficulty, topics } = req.body;
113+
114+
if (!title || !description || !difficulty) {
115+
return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json('Malformed');
116+
}
117+
118+
const payload: ICreateQuestionPayload = {
119+
title,
120+
description,
121+
difficulty,
122+
topics,
123+
};
124+
125+
try {
126+
const result = await createQuestionService(payload);
127+
if (!result.data || result.code >= 400) {
128+
return res.status(result.code).json({
129+
message: result.message ?? 'An error occurred',
130+
});
131+
}
132+
return res.status(result.code).json(result.data);
133+
} catch (error) {
134+
return res
135+
.status(StatusCodes.INTERNAL_SERVER_ERROR)
136+
.json({ success: false, message: 'An error occurred', error });
137+
}
138+
};
139+
140+
export const updateQuestion = async (req: Request, res: Response): Promise<Response> => {
141+
const { title, description, difficulty, topics } = req.body;
142+
if (!title && !description && !difficulty && (!topics || !Array.isArray(topics))) {
143+
return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json('Malformed');
144+
}
145+
146+
const payload: IUpdateQuestionPayload = {
147+
id: parseInt(req.params.questionId),
148+
title,
149+
description,
150+
difficulty,
151+
topics,
152+
};
153+
154+
try {
155+
const result = await updateQuestionService(payload);
156+
return res.status(result.code).json(result);
157+
} catch (error) {
158+
return res
159+
.status(StatusCodes.INTERNAL_SERVER_ERROR)
160+
.json({ success: false, message: 'An error occurred', error });
161+
}
162+
};
163+
164+
export const deleteQuestion = async (req: Request, res: Response): Promise<Response> => {
165+
const payload: IDeleteQuestionPayload = {
166+
id: parseInt(req.params.questionId),
167+
};
168+
169+
try {
170+
const result = await deleteQuestionService(payload);
171+
return res.status(result.code).json(result.success ? 'Ok' : result.message);
172+
} catch (error) {
173+
return res
174+
.status(StatusCodes.INTERNAL_SERVER_ERROR)
175+
.json({ success: false, message: 'An error occurred', error });
176+
}
177+
};

backend/question/src/routes/question.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {
55
getQuestions,
66
getQuestionDetails,
77
getRandomQuestion,
8+
createQuestion,
9+
updateQuestion,
10+
deleteQuestion,
811
} from '@/controller/question-controller';
912

1013
const router = Router();
@@ -17,4 +20,8 @@ router.get('/:questionId', getQuestionDetails);
1720

1821
router.post('/random', getRandomQuestion);
1922

23+
router.post('/create', createQuestion);
24+
router.put('/:questionId', updateQuestion);
25+
router.delete('/:questionId', deleteQuestion);
26+
2027
export default router;

backend/question/src/services/get/index.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { and, arrayOverlaps, eq, ilike, notInArray, sql } from 'drizzle-orm';
1+
import { and, arrayOverlaps, eq, ilike, inArray, not, sql } from 'drizzle-orm';
22

33
import { db } from '@/lib/db/index';
44
import { questions } from '@/lib/db/schema';
@@ -92,31 +92,45 @@ export const getQuestionDetailsService = async (
9292
export const getRandomQuestionService = async (
9393
payload: IGetRandomQuestionPayload
9494
): Promise<IGetRandomQuestionResponse> => {
95-
const { attemptedQuestions, difficulty, topic } = payload;
95+
const { difficulty, topic, attemptedQuestions } = payload;
9696
const whereClause = [];
9797

98+
console.log('Starting query construction');
99+
98100
if (difficulty) {
101+
console.log(`Adding difficulty filter: ${difficulty}`);
99102
whereClause.push(eq(questions.difficulty, difficulty));
100103
}
101104

102-
if (topic && topic.length > 0) {
103-
whereClause.push(arrayOverlaps(questions.topic, topic));
105+
const topicArray = (Array.isArray(topic) ? topic : [topic]).filter(
106+
(t): t is string => t !== undefined
107+
);
108+
if (topicArray.length > 0) {
109+
whereClause.push(arrayOverlaps(questions.topic, topicArray));
104110
}
105111

106112
if (attemptedQuestions && attemptedQuestions.length > 0) {
107-
whereClause.push(notInArray(questions.id, attemptedQuestions));
113+
console.log(`Excluding attempted questions: ${attemptedQuestions.join(', ')}`);
114+
whereClause.push(not(inArray(questions.id, attemptedQuestions)));
108115
}
109116

110-
// randomize the order of questions
111-
const query = db
112-
.select()
113-
.from(questions)
114-
.where(and(...whereClause))
115-
.orderBy(sql`RANDOM()`)
116-
.limit(1);
117+
console.log(`Where clause conditions: ${whereClause.length}`);
118+
119+
let query = db.select().from(questions);
120+
121+
if (whereClause.length > 0) {
122+
query = query.where(and(...whereClause)) as typeof query;
123+
}
124+
125+
query = (query as any).orderBy(sql`RANDOM()`).limit(1);
126+
127+
console.log('Executing query');
128+
console.log(query.toSQL()); // This will log the SQL query
117129

118130
const result = await query;
119131

132+
console.log(`Query result: ${JSON.stringify(result)}`);
133+
120134
if (result.length === 0) {
121135
return {
122136
code: StatusCodes.NOT_FOUND,
@@ -126,7 +140,6 @@ export const getRandomQuestionService = async (
126140
},
127141
};
128142
}
129-
130143
return {
131144
code: StatusCodes.OK,
132145
data: { question: result[0] },
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { db } from '../../lib/db/index';
2+
import { eq } from 'drizzle-orm';
3+
import { questions } from '../../lib/db/schema';
4+
import { ICreateQuestionPayload, IUpdateQuestionPayload, IDeleteQuestionPayload } from './types';
5+
6+
export const createQuestionService = async (payload: ICreateQuestionPayload) => {
7+
try {
8+
const [newQuestion] = await db
9+
.insert(questions)
10+
.values({
11+
title: payload.title,
12+
description: payload.description,
13+
difficulty: payload.difficulty,
14+
topic: payload.topics.map(String),
15+
})
16+
.returning();
17+
18+
return { success: true, code: 201, data: newQuestion };
19+
} catch (error) {
20+
console.error('Error creating question:', error);
21+
return { success: false, code: 500, message: 'Failed to create question' };
22+
}
23+
};
24+
25+
export const updateQuestionService = async (payload: IUpdateQuestionPayload) => {
26+
try {
27+
const [updatedQuestion] = await db
28+
.update(questions)
29+
.set({
30+
title: payload.title,
31+
description: payload.description,
32+
difficulty: payload.difficulty,
33+
topic: payload.topics.map(String),
34+
})
35+
.where(eq(questions.id, Number(payload.id)))
36+
.returning();
37+
38+
if (!updatedQuestion) {
39+
return { success: false, code: 404, message: 'Question not found' };
40+
}
41+
42+
return { success: true, code: 200, data: updatedQuestion };
43+
} catch (error) {
44+
console.error('Error updating question:', error);
45+
return { success: false, code: 500, message: 'Failed to update question' };
46+
}
47+
};
48+
49+
export const deleteQuestionService = async (payload: IDeleteQuestionPayload) => {
50+
try {
51+
const [deletedQuestion] = await db
52+
.delete(questions)
53+
.where(eq(questions.id, payload.id))
54+
.returning();
55+
56+
if (!deletedQuestion) {
57+
return { success: false, code: 404, message: 'Question not found' };
58+
}
59+
60+
return { success: true, code: 200, message: 'Question deleted successfully' };
61+
} catch (error) {
62+
console.error('Error deleting question:', error);
63+
return { success: false, code: 500, message: 'Failed to delete question' };
64+
}
65+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export interface ICreateQuestionPayload {
2+
title: string;
3+
description: string;
4+
difficulty: string;
5+
topics: string[];
6+
}
7+
8+
export interface IUpdateQuestionPayload extends ICreateQuestionPayload {
9+
id: number;
10+
}
11+
12+
export interface IDeleteQuestionPayload {
13+
id: number;
14+
}

0 commit comments

Comments
 (0)