Skip to content

Commit a428854

Browse files
authored
Merge pull request #22 from CS3219-AY2425S1/PEER-215-Search-By-Title
Search Questions By Title
2 parents 24d1e20 + e7d87ca commit a428854

File tree

11 files changed

+238
-13
lines changed

11 files changed

+238
-13
lines changed

backend/question/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ services:
3939
- question-db
4040

4141
volumes:
42-
question-db-docker:
42+
question-db-docker:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE "questions" ADD COLUMN "attempted" boolean DEFAULT false NOT NULL;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE "questions" DROP COLUMN IF EXISTS "attempted";
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"id": "9fdd4b9a-8576-4be3-bfbc-c2d682d60b45",
3+
"prevId": "36d01d6f-4eb8-4c12-a89f-89295da72dcc",
4+
"version": "7",
5+
"dialect": "postgresql",
6+
"tables": {
7+
"public.questions": {
8+
"name": "questions",
9+
"schema": "",
10+
"columns": {
11+
"id": {
12+
"name": "id",
13+
"type": "uuid",
14+
"primaryKey": true,
15+
"notNull": true,
16+
"default": "gen_random_uuid()"
17+
},
18+
"title": {
19+
"name": "title",
20+
"type": "varchar(255)",
21+
"primaryKey": false,
22+
"notNull": true
23+
},
24+
"difficulty": {
25+
"name": "difficulty",
26+
"type": "varchar(50)",
27+
"primaryKey": false,
28+
"notNull": true
29+
},
30+
"topic": {
31+
"name": "topic",
32+
"type": "varchar(255)[]",
33+
"primaryKey": false,
34+
"notNull": true
35+
},
36+
"description": {
37+
"name": "description",
38+
"type": "text",
39+
"primaryKey": false,
40+
"notNull": true
41+
},
42+
"attempted": {
43+
"name": "attempted",
44+
"type": "boolean",
45+
"primaryKey": false,
46+
"notNull": true,
47+
"default": false
48+
},
49+
"created_at": {
50+
"name": "created_at",
51+
"type": "timestamp (6) with time zone",
52+
"primaryKey": false,
53+
"notNull": false,
54+
"default": "now()"
55+
},
56+
"updated_at": {
57+
"name": "updated_at",
58+
"type": "timestamp (6) with time zone",
59+
"primaryKey": false,
60+
"notNull": false,
61+
"default": "now()"
62+
}
63+
},
64+
"indexes": {},
65+
"foreignKeys": {},
66+
"compositePrimaryKeys": {},
67+
"uniqueConstraints": {}
68+
}
69+
},
70+
"enums": {},
71+
"schemas": {},
72+
"sequences": {},
73+
"_meta": {
74+
"columns": {},
75+
"schemas": {},
76+
"tables": {}
77+
}
78+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"id": "9ad800fd-5b56-4936-aaf7-94b6243549ce",
3+
"prevId": "9fdd4b9a-8576-4be3-bfbc-c2d682d60b45",
4+
"version": "7",
5+
"dialect": "postgresql",
6+
"tables": {
7+
"public.questions": {
8+
"name": "questions",
9+
"schema": "",
10+
"columns": {
11+
"id": {
12+
"name": "id",
13+
"type": "uuid",
14+
"primaryKey": true,
15+
"notNull": true,
16+
"default": "gen_random_uuid()"
17+
},
18+
"title": {
19+
"name": "title",
20+
"type": "varchar(255)",
21+
"primaryKey": false,
22+
"notNull": true
23+
},
24+
"difficulty": {
25+
"name": "difficulty",
26+
"type": "varchar(50)",
27+
"primaryKey": false,
28+
"notNull": true
29+
},
30+
"topic": {
31+
"name": "topic",
32+
"type": "varchar(255)[]",
33+
"primaryKey": false,
34+
"notNull": true
35+
},
36+
"description": {
37+
"name": "description",
38+
"type": "text",
39+
"primaryKey": false,
40+
"notNull": true
41+
},
42+
"created_at": {
43+
"name": "created_at",
44+
"type": "timestamp (6) with time zone",
45+
"primaryKey": false,
46+
"notNull": false,
47+
"default": "now()"
48+
},
49+
"updated_at": {
50+
"name": "updated_at",
51+
"type": "timestamp (6) with time zone",
52+
"primaryKey": false,
53+
"notNull": false,
54+
"default": "now()"
55+
}
56+
},
57+
"indexes": {},
58+
"foreignKeys": {},
59+
"compositePrimaryKeys": {},
60+
"uniqueConstraints": {}
61+
}
62+
},
63+
"enums": {},
64+
"schemas": {},
65+
"sequences": {},
66+
"_meta": {
67+
"columns": {},
68+
"schemas": {},
69+
"tables": {}
70+
}
71+
}

backend/question/drizzle/meta/_journal.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@
88
"when": 1727390805855,
99
"tag": "0000_initial_schema",
1010
"breakpoints": true
11+
},
12+
{
13+
"idx": 1,
14+
"version": "7",
15+
"when": 1727504673110,
16+
"tag": "0001_neat_skullbuster",
17+
"breakpoints": true
18+
},
19+
{
20+
"idx": 2,
21+
"version": "7",
22+
"when": 1727510874424,
23+
"tag": "0002_glorious_onslaught",
24+
"breakpoints": true
1125
}
1226
]
1327
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Request, Response } from 'express';
2+
import { searchQuestionsByTitleService } from '../services/get/index';
3+
4+
export const searchQuestionsByTitle = async (req: Request, res: Response): Promise<Response> => {
5+
const { title } = req.query;
6+
const page = parseInt(req.query.page as string) || 1;
7+
const limit = parseInt(req.query.limit as string) || 10;
8+
9+
if (!title) {
10+
return res.status(200);
11+
}
12+
13+
try {
14+
const result = await searchQuestionsByTitleService(title.toString(), page, limit);
15+
return res.status(200).json(result);
16+
} catch (error) {
17+
return res.status(500).json({ success: false, message: 'An error occurred', error });
18+
}
19+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Router } from 'express';
2+
import { searchQuestionsByTitle } from '../controller/search-controller';
3+
4+
const router = Router();
5+
6+
router.get('/search', searchQuestionsByTitle);
7+
8+
export default router;

backend/question/src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { exit } from 'process';
2-
2+
import questionsRouter from './routes/question-routes';
33
import express, { json } from 'express';
44
import pino from 'pino-http';
55
import { sql } from 'drizzle-orm';
@@ -10,7 +10,7 @@ import { logger } from '@/lib/utils';
1010
const app = express();
1111
app.use(pino());
1212
app.use(json());
13-
13+
app.use('/questions', questionsRouter);
1414
app.get('/', async (_req, res) => {
1515
res.json({
1616
message: 'OK',
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { db } from '../../lib/db/index';
2+
import { sql } from 'drizzle-orm';
3+
import { questions } from '../../lib/db/schema';
4+
import { IGetQuestionsResponse } from '../get/types';
5+
6+
export const searchQuestionsByTitleService = async (
7+
title: string,
8+
page: number,
9+
limit: number
10+
): Promise<IGetQuestionsResponse> => {
11+
const searchPattern = `%${title}%`;
12+
const effectivePage = page ?? 1;
13+
const effectiveLimit = limit ?? 10;
14+
const offset = (effectivePage - 1) * effectiveLimit;
15+
16+
// Query the database for questions matching the title
17+
const results = await db
18+
.select({
19+
id: questions.id,
20+
title: questions.title,
21+
difficulty: questions.difficulty,
22+
topic: questions.topic,
23+
})
24+
.from(questions)
25+
.where(sql`${questions.title} ILIKE ${searchPattern}`) // Use ILIKE for case-insensitive matching
26+
.limit(effectiveLimit)
27+
.offset(offset);
28+
29+
// Return the results as per IGetQuestionsResponse format
30+
return {
31+
code: 200,
32+
data: {
33+
questions: results, // Directly returning the query results
34+
totalQuestions: results.length, // Count of questions returned
35+
},
36+
};
37+
};

0 commit comments

Comments
 (0)