Skip to content
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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions README.md
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.
1 change: 1 addition & 0 deletions services/question/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
/services/question/node_modules/

# testing
/coverage
Expand Down
103 changes: 103 additions & 0 deletions services/question/README.md
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)

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?

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.

Copy link

@samuelim01 samuelim01 Sep 17, 2024

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 and offset in the get all questions endpoint for table pagination and filtering 😄

- `topics` - The topic of the question (Required)

Choose a reason for hiding this comment

The 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?

Choose a reason for hiding this comment

The 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)

Choose a reason for hiding this comment

The 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
3 changes: 1 addition & 2 deletions services/question/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const app: Express = express();

// Middleware
app.use(morgan('dev'));

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

Expand All @@ -23,6 +22,6 @@ app.use(

// Routes
app.use('/', router);
app.use('/', questionRouter);
app.use('/questions', questionRouter);

export default app;
109 changes: 103 additions & 6 deletions services/question/src/controllers/questionController.ts
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);

Check failure on line 14 in services/question/src/controllers/questionController.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Replace `"All·questions·retrieved·successfully"` with `'All·questions·retrieved·successfully'`
} 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);

Check failure on line 41 in services/question/src/controllers/questionController.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Replace `"Question·with·ID·retrieved·successfully"` with `'Question·with·ID·retrieved·successfully'`
} 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

Check failure on line 55 in services/question/src/controllers/questionController.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Insert `;`
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);

Check failure on line 83 in services/question/src/controllers/questionController.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Replace `"Questions·with·Parameters·retrieved·successfully"` with `'Questions·with·Parameters·retrieved·successfully'`
} 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);

Check failure on line 103 in services/question/src/controllers/questionController.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Replace `"Topics·retrieved·successfully"` with `'Topics·retrieved·successfully'`
} catch (error) {
console.error('Error in getTopics:', error);
handleError(res, error, 'Failed to retrieve topics');
}
};

Check failure on line 108 in services/question/src/controllers/questionController.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Insert `⏎`
4 changes: 3 additions & 1 deletion services/question/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
2 changes: 1 addition & 1 deletion services/question/src/models/questionModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const questionSchema = new Schema<IQuestion>(
difficulty: {
type: String,
required: true,
enum: ['Easy', 'Medium', 'Difficult'],
enum: ['Easy', 'Medium', 'Hard'],
},
},
{ versionKey: false },
Expand Down
27 changes: 23 additions & 4 deletions services/question/src/routes/questionRoutes.ts
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;
9 changes: 7 additions & 2 deletions services/question/src/utils/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import fs from 'fs/promises';
import { IQuestion } from '../models/questionModel';

export async function getDemoQuestions(): Promise<IQuestion[]> {
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');
}
}
51 changes: 51 additions & 0 deletions services/question/src/utils/helpers.ts
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

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Unexpected any. Specify a different type

Check failure on line 10 in services/question/src/utils/helpers.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Unexpected any. Specify a different type

Check failure on line 10 in services/question/src/utils/helpers.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

'message' is assigned a value but never used
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) => {

Check failure on line 22 in services/question/src/utils/helpers.ts

View workflow job for this annotation

GitHub Actions / build-service (services/question)

Unexpected any. Specify a different type
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,
});
};