-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update Question Service #6
Changes from all commits
85df44d
909b93a
fdcd930
eca9c80
29b0cff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
[![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. | ||
### 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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
/node_modules | ||
/.pnp | ||
.pnp.js | ||
/services/question/node_modules/ | ||
|
||
# testing | ||
/coverage | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we allow users to submit multiple topics that they are interested in? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah agreed. This probably will improve user experience in matching too since the user would have a wider range of topics. |
||
- `languages` - The language of the question (Required) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean that we are going to customize the code template for each question? Personally, I'm not too keen on doing so, and hence I don't think we need this field. |
||
- `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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'); | ||
} | ||
}; | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) => { | ||
Check failure on line 10 in services/question/src/utils/helpers.ts GitHub Actions / build-service (services/question)
Check failure on line 10 in services/question/src/utils/helpers.ts GitHub Actions / build-service (services/question)
|
||
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, | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this endpoint retrieve a single question?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh the description I wrote was a bit misleading. I was thinking we could just have set it by default to 1 for finding a question for a user. Then for values other than 1, I was thinking we could have it just in case there's some other usage for it (eg; retrieval of questions that matches the param for admin to look through). But I guess the randomised nature of this wouldn't really work that well for the example I gave LOL unless there's another boolean flag to indicate if we want random or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep sounds good, we could have optional parameters
topics
,difficulty
,limit
andoffset
in the get all questions endpoint for table pagination and filtering 😄