From 85df44d0b86d509a7b7dc6bbc3e8cdcc6616c30b Mon Sep 17 00:00:00 2001 From: KhoonSun47 Date: Sun, 15 Sep 2024 22:58:56 +0800 Subject: [PATCH 1/4] Update README.md for Question Service --- README.md | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5ddee1cbf1..57d341b438 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,107 @@ [![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/bzPrOe11) # CS3219 Project (PeerPrep) - AY2425S1 -## Group: Gxx +## Group: G03 -### Note: -- You can choose to develop individual microservices within separate folders within this repository **OR** use individual repositories (all public) for each microservice. -- In the latter scenario, you should enable sub-modules on this GitHub classroom repository to manage the development/deployment **AND** add your mentor to the individual repositories as a collaborator. -- The teaching team should be given access to the repositories as we may require viewing the history of the repository in case of any disputes or disagreements. +# Question Service User Guide + +## Get All Questions +This endpoint allows the retrieval of all the questions in the database. + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions` + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, all questions are returned | +| 500 (Internal Server Error) | Unexpected error in the database or server | + +**Example of Response Body for Success**: +TBC + +## Get Question by ID +This endpoint allows the retrieval of the question by using the question ID. + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions/{questionId}` + +### Parameters: + +- Required: `questionId` path parameter + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, question corresponding to the questionID is returned | +| 404 (Not Found) | Question with the specified questionID not found | +| 500 (Internal Server Error) | Unexpected error in the database or server | + +**Example of Response Body for Success**: +TBC + +## Get Question by Parameters +This endpoint allows the retrieval of a random question that matches the parameters provided. + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions/search` + +### Parameters: + +- `limit` - The number of questions to be returned (Required) +- `topics` - The topic of the question (Required) +- `languages` - The language of the question (Required) +- `difficulty` - The difficulty of question (Required) + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, question corresponding to the limit, topics, languages and difficulty is returned | +| 400 (Bad Request) | Missing fields | +| 404 (Not Found) | Question with the specified parameter(s) not found | +| 500 (Internal Server Error) | Unexpected error in the database or server | + +**Example of Response Body for Success**: +TBC + +## Get Topics +This endpoint retrieves all unique topics in the database (e.g. “Sorting”, “OOP”, “DFS”, etc…) + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions/topics` + +### Headers: + +- Required: - + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, all topics are returned | +| 500 (Internal Server Error) | The server encountered an error and could not complete the request | + +**Example of Response Body for Success**: +TBC + +## Get Language +This endpoint retrieves all unique languages in the database (e.g, Java, C++, C, etc..) + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions/languages` + +### Headers: + +- Required: - + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, all languages are returned | +| 500 (Internal Server Error) | The server encountered an error and could not complete the request | + +**Example of Response Body for Success**: +TBC From 909b93ac79b8d42f7ceb01b2e4739e95c7f068bf Mon Sep 17 00:00:00 2001 From: KhoonSun47 Date: Sun, 15 Sep 2024 23:11:13 +0800 Subject: [PATCH 2/4] Update README.md --- .idea/.gitignore | 8 +++ README.md | 107 ++---------------------------------- services/question/README.md | 103 ++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 103 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 services/question/README.md diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000..13566b81b0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/README.md b/README.md index 57d341b438..1f1edee3a5 100644 --- a/README.md +++ b/README.md @@ -2,106 +2,7 @@ # CS3219 Project (PeerPrep) - AY2425S1 ## Group: G03 -# Question Service User Guide - -## Get All Questions -This endpoint allows the retrieval of all the questions in the database. - -- **HTTP Method**: `GET` -- **Endpoint**: `/questions` - -### Responses: - -| Response Code | Explanation | -|---------------|-----------------------------------------------------| -| 200 (OK) | Success, all questions are returned | -| 500 (Internal Server Error) | Unexpected error in the database or server | - -**Example of Response Body for Success**: -TBC - -## Get Question by ID -This endpoint allows the retrieval of the question by using the question ID. - -- **HTTP Method**: `GET` -- **Endpoint**: `/questions/{questionId}` - -### Parameters: - -- Required: `questionId` path parameter - -### Responses: - -| Response Code | Explanation | -|---------------|-----------------------------------------------------| -| 200 (OK) | Success, question corresponding to the questionID is returned | -| 404 (Not Found) | Question with the specified questionID not found | -| 500 (Internal Server Error) | Unexpected error in the database or server | - -**Example of Response Body for Success**: -TBC - -## Get Question by Parameters -This endpoint allows the retrieval of a random question that matches the parameters provided. - -- **HTTP Method**: `GET` -- **Endpoint**: `/questions/search` - -### Parameters: - -- `limit` - The number of questions to be returned (Required) -- `topics` - The topic of the question (Required) -- `languages` - The language of the question (Required) -- `difficulty` - The difficulty of question (Required) - -### Responses: - -| Response Code | Explanation | -|---------------|-----------------------------------------------------| -| 200 (OK) | Success, question corresponding to the limit, topics, languages and difficulty is returned | -| 400 (Bad Request) | Missing fields | -| 404 (Not Found) | Question with the specified parameter(s) not found | -| 500 (Internal Server Error) | Unexpected error in the database or server | - -**Example of Response Body for Success**: -TBC - -## Get Topics -This endpoint retrieves all unique topics in the database (e.g. “Sorting”, “OOP”, “DFS”, etc…) - -- **HTTP Method**: `GET` -- **Endpoint**: `/questions/topics` - -### Headers: - -- Required: - - -### Responses: - -| Response Code | Explanation | -|---------------|-----------------------------------------------------| -| 200 (OK) | Success, all topics are returned | -| 500 (Internal Server Error) | The server encountered an error and could not complete the request | - -**Example of Response Body for Success**: -TBC - -## Get Language -This endpoint retrieves all unique languages in the database (e.g, Java, C++, C, etc..) - -- **HTTP Method**: `GET` -- **Endpoint**: `/questions/languages` - -### Headers: - -- Required: - - -### Responses: - -| Response Code | Explanation | -|---------------|-----------------------------------------------------| -| 200 (OK) | Success, all languages are returned | -| 500 (Internal Server Error) | The server encountered an error and could not complete the request | - -**Example of Response Body for Success**: -TBC +### Note: +- You can choose to develop individual microservices within separate folders within this repository **OR** use individual repositories (all public) for each microservice. +- In the latter scenario, you should enable sub-modules on this GitHub classroom repository to manage the development/deployment **AND** add your mentor to the individual repositories as a collaborator. +- The teaching team should be given access to the repositories as we may require viewing the history of the repository in case of any disputes or disagreements. diff --git a/services/question/README.md b/services/question/README.md new file mode 100644 index 0000000000..6d952159e9 --- /dev/null +++ b/services/question/README.md @@ -0,0 +1,103 @@ +# Question Service User Guide + +## Get All Questions +This endpoint allows the retrieval of all the questions in the database. + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions` + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, all questions are returned | +| 500 (Internal Server Error) | Unexpected error in the database or server | + +**Example of Response Body for Success**: +TBC + +## Get Question by ID +This endpoint allows the retrieval of the question by using the question ID. + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions/{questionId}` + +### Parameters: + +- Required: `questionId` path parameter + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, question corresponding to the questionID is returned | +| 404 (Not Found) | Question with the specified questionID not found | +| 500 (Internal Server Error) | Unexpected error in the database or server | + +**Example of Response Body for Success**: +TBC + +## Get Question by Parameters +This endpoint allows the retrieval of a random question that matches the parameters provided. + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions/search` + +### Parameters: + +- `limit` - The number of questions to be returned (Required) +- `topics` - The topic of the question (Required) +- `languages` - The language of the question (Required) +- `difficulty` - The difficulty of question (Required) + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, question corresponding to the limit, topics, languages and difficulty is returned | +| 400 (Bad Request) | Missing fields | +| 404 (Not Found) | Question with the specified parameter(s) not found | +| 500 (Internal Server Error) | Unexpected error in the database or server | + +**Example of Response Body for Success**: +TBC + +## Get Topics +This endpoint retrieves all unique topics in the database (e.g. “Sorting”, “OOP”, “DFS”, etc…) + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions/topics` + +### Headers: + +- Required: - + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, all topics are returned | +| 500 (Internal Server Error) | The server encountered an error and could not complete the request | + +**Example of Response Body for Success**: +TBC + +## Get Language +This endpoint retrieves all unique languages in the database (e.g, Java, C++, C, etc..) + +- **HTTP Method**: `GET` +- **Endpoint**: `/questions/languages` + +### Headers: + +- Required: - + +### Responses: + +| Response Code | Explanation | +|---------------|-----------------------------------------------------| +| 200 (OK) | Success, all languages are returned | +| 500 (Internal Server Error) | The server encountered an error and could not complete the request | + +**Example of Response Body for Success**: +TBC From eca9c809587f738bf63e4a431134701afa9e7a77 Mon Sep 17 00:00:00 2001 From: KhoonSun47 Date: Fri, 20 Sep 2024 15:49:32 +0800 Subject: [PATCH 3/4] Add node_modules to .gitignore --- services/question/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/services/question/.gitignore b/services/question/.gitignore index 931232e706..052504ff33 100644 --- a/services/question/.gitignore +++ b/services/question/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +/services/question/node_modules/ # testing /coverage From 29b0cff0f1edbb7d53b6e43520cc4992e96169d0 Mon Sep 17 00:00:00 2001 From: KhoonSun47 Date: Fri, 20 Sep 2024 23:40:37 +0800 Subject: [PATCH 4/4] Add new get endpoint and update models - questionController.ts: Implemented new get endpoint for retrieving questions. - questionModel.ts: Updated `Difficulty` enum to `Hard` enum. - questionRoutes.ts: Added new routes for handling get requests. - helper.ts: Added helper function for improved error handling. --- services/question/src/app.ts | 3 +- .../src/controllers/questionController.ts | 109 +++++++++++++++++- services/question/src/models/index.ts | 4 +- services/question/src/models/questionModel.ts | 2 +- .../question/src/routes/questionRoutes.ts | 27 ++++- services/question/src/utils/data.ts | 9 +- services/question/src/utils/helpers.ts | 51 ++++++++ 7 files changed, 189 insertions(+), 16 deletions(-) create mode 100644 services/question/src/utils/helpers.ts diff --git a/services/question/src/app.ts b/services/question/src/app.ts index ad51519cca..53a5b4acc0 100644 --- a/services/question/src/app.ts +++ b/services/question/src/app.ts @@ -9,7 +9,6 @@ const app: Express = express(); // Middleware app.use(morgan('dev')); - app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); @@ -23,6 +22,6 @@ app.use( // Routes app.use('/', router); -app.use('/', questionRouter); +app.use('/questions', questionRouter); export default app; diff --git a/services/question/src/controllers/questionController.ts b/services/question/src/controllers/questionController.ts index 915d7bdb97..e0e949d111 100644 --- a/services/question/src/controllers/questionController.ts +++ b/services/question/src/controllers/questionController.ts @@ -1,11 +1,108 @@ import { Request, Response } from 'express'; +import { handleError, handleNotFound, handleBadRequest, handleSuccess } from '../utils/helpers'; import { Question } from '../models/questionModel'; +/** + * This endpoint allows the retrieval of all the questions in the database. + * @param req + * @param res + */ export const getQuestions = async (req: Request, res: Response) => { - const questions = await Question.find(); - const questionTitles = questions.map(q => q.title); - res.status(200).json({ - message: 'These are all the question titles:' + questionTitles, - }); - return; + try { + const questions = await Question.find(); + + handleSuccess(res, "All questions retrieved successfully", questions); + } catch (error) { + console.error('Error in getQuestions:', error); + handleError(res, error, 'Failed to retrieve questions'); + } +}; + +/** + * This endpoint allows the retrieval of the question by using the question ID. + * @param req + * @param res + */ +export const getQuestionById = async (req: Request, res: Response) => { + const { id } = req.params; + + const newId = parseInt(id, 10); + if (isNaN(newId)) { + return handleBadRequest(res, 'Invalid question ID'); + } + + try { + const question = await Question.findOne({ id: newId }); + + if (!question) { + return handleNotFound(res, 'Question not found'); + } + + handleSuccess(res, "Question with ID retrieved successfully", question); + } catch (error) { + console.error('Error in getQuestionById:', error); + handleError(res, error, 'Failed to retrieve question'); + } +}; + +/** + * This endpoint allows the retrieval of a random question that matches the parameters provided. + * @param req + * @param res + */ +export const getQuestionByParameters = async (req: Request, res: Response) => { + const { limit, topics, difficulty } = req.query; + const stringLimit = limit as string + const newLimit = parseInt(stringLimit, 10); + + if (!topics) { + return handleBadRequest(res, 'Topics are required'); + } + if (!difficulty) { + return handleBadRequest(res, 'Difficulty is required'); + } + if (isNaN(newLimit)) { + return handleBadRequest(res, 'Limit must be a valid positive integer'); + } + if (newLimit <= 0) { + return handleBadRequest(res, 'Limit must be more than 0'); + } + + try { + const topicsArray = (topics as string).split(','); + const query = { + topics: { $in: topicsArray }, + difficulty: difficulty, + }; + const questions = await Question.find(query).limit(newLimit); + + if (!questions || questions.length === 0) { + return handleNotFound(res, 'No questions found with the given parameters'); + } + + handleSuccess(res, "Questions with Parameters retrieved successfully", questions); + } catch (error) { + console.error('Error in getQuestionByParameters:', error); + handleError(res, error, 'Failed to search for questions'); + } }; + +/** + * This endpoint retrieves all unique topics in the database (e.g. “Sorting”, “OOP”, “DFS”, etc…) + * @param req + * @param res + */ +export const getTopics = async (req: Request, res: Response) => { + try { + const topics = await Question.distinct('topics'); + + if (!topics || topics.length === 0) { + return handleNotFound(res, 'No topics found'); + } + + handleSuccess(res, "Topics retrieved successfully", topics); + } catch (error) { + console.error('Error in getTopics:', error); + handleError(res, error, 'Failed to retrieve topics'); + } +}; \ No newline at end of file diff --git a/services/question/src/models/index.ts b/services/question/src/models/index.ts index b78620a016..90596e07ce 100644 --- a/services/question/src/models/index.ts +++ b/services/question/src/models/index.ts @@ -4,8 +4,10 @@ import { IQuestion, Question } from './questionModel'; export async function connectToDB() { const mongoURI = process.env.NODE_ENV === 'production' ? process.env.DB_CLOUD_URI : process.env.DB_LOCAL_URI; + console.log('MongoDB URI:', mongoURI); + if (!mongoURI) { - throw Error('MongoDB URI not specified'); + throw new Error('MongoDB URI not specified'); } else if (!process.env.DB_USERNAME || !process.env.DB_PASSWORD) { throw Error('MongoDB credentials not specified'); } diff --git a/services/question/src/models/questionModel.ts b/services/question/src/models/questionModel.ts index 84de364fb1..4e14460f10 100644 --- a/services/question/src/models/questionModel.ts +++ b/services/question/src/models/questionModel.ts @@ -37,7 +37,7 @@ const questionSchema = new Schema( difficulty: { type: String, required: true, - enum: ['Easy', 'Medium', 'Difficult'], + enum: ['Easy', 'Medium', 'Hard'], }, }, { versionKey: false }, diff --git a/services/question/src/routes/questionRoutes.ts b/services/question/src/routes/questionRoutes.ts index c7cb207f31..8896825ad4 100644 --- a/services/question/src/routes/questionRoutes.ts +++ b/services/question/src/routes/questionRoutes.ts @@ -1,7 +1,26 @@ import { Router } from 'express'; -import { getQuestions } from '../controllers/questionController'; -const router = Router(); +import { getQuestions, getQuestionById, getQuestionByParameters, getTopics } from '../controllers/questionController'; -router.get('/questions', getQuestions); +const questionRouter = Router(); -export default router; +/** + * To Test: curl -X GET "http://localhost:8081/questions/search?topics=Algorithms,Data%20Structures&difficulty=Easy&limit=5" + */ +questionRouter.get('/search', getQuestionByParameters); + +/** + * To Test: http://localhost:8081/topics + */ +questionRouter.get('/topics', getTopics); + +/** + * To Test: http://localhost:8081/questions/ + */ +questionRouter.get('/', getQuestions); + +/** + * To Test: http://localhost:8081/questions/1 + */ +questionRouter.get('/:id', getQuestionById); + +export default questionRouter; diff --git a/services/question/src/utils/data.ts b/services/question/src/utils/data.ts index 2755c73ebd..dd29e9da52 100644 --- a/services/question/src/utils/data.ts +++ b/services/question/src/utils/data.ts @@ -2,6 +2,11 @@ import fs from 'fs/promises'; import { IQuestion } from '../models/questionModel'; export async function getDemoQuestions(): Promise { - const data = await fs.readFile('./src/data/questions.json', { encoding: 'utf8' }); - return JSON.parse(data); + try { + const data = await fs.readFile('./src/data/questions.json', { encoding: 'utf8' }); + return JSON.parse(data); + } catch (error) { + console.error('Error reading questions from JSON:', error); + throw new Error('Failed to read demo questions'); + } } diff --git a/services/question/src/utils/helpers.ts b/services/question/src/utils/helpers.ts new file mode 100644 index 0000000000..eb8bd85a61 --- /dev/null +++ b/services/question/src/utils/helpers.ts @@ -0,0 +1,51 @@ +import { Response } from 'express'; + +/** + * 500: Unexpected error in the database/server + * @param res + * @param error + * @param message + * @param statusCode + */ +export const handleError = (res: any, error: any, message = 'An unexpected error occurred', statusCode = 500) => { + console.error(error); + res.status(statusCode).json({ error }); +}; + +/** + * 400: Bad Request + * @param res + * @param error + * @param message + * @param statusCode + */ +export const handleBadRequest = (res: any, error: any, message = 'Bad Request', statusCode = 400) => { + console.error(error); + res.status(statusCode).json({ error }); +}; + +/** + * 404: Not Found + * @param res + * @param error + * @param message + * @param statusCode + */ +export const handleNotFound = (res: any, error: any, message = 'Not Found', statusCode = 404) => { + console.error(error); + res.status(statusCode).json({ error }); +}; + +/** + * 200: Success + * @param res + * @param message + * @param data + */ +export const handleSuccess = (res: Response, message: string, data: any) => { + res.status(200).json({ + status: 'success', + message, + data, + }); +}; \ No newline at end of file