Skip to content

Commit b76969f

Browse files
committed
Search by title
1 parent 2482ae0 commit b76969f

File tree

10 files changed

+153
-14
lines changed

10 files changed

+153
-14
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: 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+
}

backend/question/drizzle/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
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
1118
}
1219
]
1320
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
7+
if (!title) {
8+
return res.status(400).json({ message: 'Title is required' });
9+
}
10+
11+
try {
12+
const result = await searchQuestionsByTitleService(title.toString());
13+
return res.status(200).json(result);
14+
} catch (error) {
15+
return res.status(500).json({ success: false, message: 'An error occurred', error });
16+
}
17+
};
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { pgTable, uuid, varchar, text, timestamp } from 'drizzle-orm/pg-core';
1+
import { pgTable, uuid, varchar, text, timestamp, boolean } from 'drizzle-orm/pg-core';
22

33
export const questions = pgTable('questions', {
44
id: uuid('id').primaryKey().defaultRandom(),
55
title: varchar('title', { length: 255 }).notNull(),
66
difficulty: varchar('difficulty', { length: 50 }).notNull(),
77
topic: varchar('topic', { length: 255 }).array().notNull(),
88
description: text('description').notNull(),
9+
attempted: boolean('attempted').notNull().default(false),
910
createdAt: timestamp('created_at', { precision: 6, withTimezone: true }).defaultNow(),
1011
updatedAt: timestamp('updated_at', { precision: 6, withTimezone: true }).defaultNow(),
1112
});
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
): Promise<IGetQuestionsResponse> => {
9+
const searchPattern = `%${title}%`;
10+
11+
// Query the database for questions matching the title
12+
const results = await db
13+
.select({
14+
id: questions.id,
15+
title: questions.title,
16+
difficulty: questions.difficulty,
17+
topic: questions.topic,
18+
attempted: questions.attempted,
19+
})
20+
.from(questions)
21+
.where(sql`${questions.title} ILIKE ${searchPattern}`); // Use ILIKE for case-insensitive matching
22+
23+
// Return the results as per IGetQuestionsResponse format
24+
return {
25+
code: 200,
26+
data: {
27+
questions: results, // Directly returning the query results
28+
totalQuestions: results.length, // Count of questions returned
29+
},
30+
};
31+
};

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

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,14 @@ export type IGetQuestionsPayload = {
1414
};
1515

1616
export type IGetQuestionsResponse = IServiceResponse<{
17-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
1817
questions: Array<{
19-
// TODO: Add schema from DB
20-
/**
21-
* - name
22-
* - number
23-
* - difficulty
24-
* - topic
25-
* - attempted?: May need joining with users table, or not
26-
*/
18+
id: string; // name or title of the question
19+
title: string; // question's unique identifier or number
20+
difficulty: string; // difficulty level (e.g., 'easy', 'medium', 'hard')
21+
topic: Array<string>; // array of topics the question belongs to
22+
attempted?: boolean; // whether the user has attempted this question
2723
}>;
28-
totalQuestions: number;
24+
totalQuestions: number; // total number of questions matching the query
2925
}>;
3026

3127
//=============================================================================

0 commit comments

Comments
 (0)