Skip to content

Commit c9cec7b

Browse files
Improve questions (#49)
* Wrap delete questions in single API call * Add new POST `questions/delete` endpoint * Update question service README * Update frontend to use new endpoint * Ensure question table is responsive to fit smaller viewports * Fix question README * Fix comments * Update return type of getQuestionByID * Revert "Update return type of getQuestionByID" This reverts commit cb11b5a. * Update return type of getQuestionByID --------- Co-authored-by: limcaaarl <[email protected]>
1 parent d3a9e4c commit c9cec7b

File tree

7 files changed

+109
-31
lines changed

7 files changed

+109
-31
lines changed

frontend/src/_services/question.service.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular
22
import { Injectable } from '@angular/core';
33
import { API_CONFIG } from '../app/api.config';
44
import { catchError, Observable, throwError } from 'rxjs';
5-
import { SingleQuestionResponse, QuestionResponse, QuestionBody } from '../app/questions/question.model';
5+
import {
6+
SingleQuestionResponse,
7+
QuestionResponse,
8+
QuestionBody,
9+
MessageOnlyResponse,
10+
} from '../app/questions/question.model';
611
import { TopicResponse } from '../app/questions/topic.model';
712

813
@Injectable({
914
providedIn: 'root',
1015
})
1116
export class QuestionService {
12-
private baseUrl = API_CONFIG.baseUrl;
17+
private baseUrl = API_CONFIG.baseUrl + '/questions';
1318

1419
private httpOptions = {
1520
headers: new HttpHeaders({
@@ -41,11 +46,11 @@ export class QuestionService {
4146
}
4247

4348
// send request
44-
return this.http.get<QuestionResponse>(this.baseUrl + '/questions', { params });
49+
return this.http.get<QuestionResponse>(this.baseUrl, { params });
4550
}
4651

47-
getQuestionByID(id: number): Observable<QuestionResponse> {
48-
return this.http.get<QuestionResponse>(this.baseUrl + '/questions/' + id);
52+
getQuestionByID(id: number): Observable<SingleQuestionResponse> {
53+
return this.http.get<SingleQuestionResponse>(this.baseUrl + '/' + id);
4954
}
5055

5156
getQuestionByParam(topics: string[], difficulty: string, limit?: number): Observable<QuestionResponse> {
@@ -56,28 +61,32 @@ export class QuestionService {
5661
}
5762
params = params.append('topics', topics.join(',')).append('difficulty', difficulty);
5863

59-
return this.http.get<QuestionResponse>(this.baseUrl + '/questions/search', { params });
64+
return this.http.get<QuestionResponse>(this.baseUrl + '/search', { params });
6065
}
6166

6267
getTopics(): Observable<TopicResponse> {
63-
return this.http.get<TopicResponse>(this.baseUrl + '/questions/topics');
68+
return this.http.get<TopicResponse>(this.baseUrl + '/topics');
6469
}
6570

6671
addQuestion(question: QuestionBody): Observable<SingleQuestionResponse> {
6772
return this.http
68-
.post<SingleQuestionResponse>(this.baseUrl + '/questions', question, this.httpOptions)
73+
.post<SingleQuestionResponse>(this.baseUrl, question, this.httpOptions)
6974
.pipe(catchError(this.handleError));
7075
}
7176

7277
updateQuestion(id: number, question: QuestionBody): Observable<SingleQuestionResponse> {
7378
return this.http
74-
.put<SingleQuestionResponse>(this.baseUrl + '/questions/' + id, question, this.httpOptions)
79+
.put<SingleQuestionResponse>(this.baseUrl + '/' + id, question, this.httpOptions)
7580
.pipe(catchError(this.handleError));
7681
}
7782

7883
deleteQuestion(id: number): Observable<SingleQuestionResponse> {
84+
return this.http.delete<SingleQuestionResponse>(this.baseUrl + '/' + id).pipe(catchError(this.handleError));
85+
}
86+
87+
deleteQuestions(ids: number[]): Observable<MessageOnlyResponse> {
7988
return this.http
80-
.delete<SingleQuestionResponse>(this.baseUrl + '/questions/' + id)
89+
.post<MessageOnlyResponse>(this.baseUrl + '/delete', { ids }, this.httpOptions)
8190
.pipe(catchError(this.handleError));
8291
}
8392

frontend/src/app/questions/question.model.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
export interface QuestionResponse {
1+
export interface BaseResponse {
22
status: string;
33
message: string;
4+
}
5+
6+
export interface MessageOnlyResponse extends BaseResponse {
7+
data: null;
8+
}
9+
10+
export interface QuestionResponse extends BaseResponse {
411
data?: Question[] | null;
512
}
613

7-
export interface SingleQuestionResponse {
8-
status: string;
9-
message: string;
14+
export interface SingleQuestionResponse extends BaseResponse {
1015
data: Question;
1116
}
1217

frontend/src/app/questions/questions.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
[value]="questions"
3232
[(selection)]="selectedQuestions"
3333
datakey="id"
34-
[tableStyle]="{ 'table-layout': 'fixed' }"
34+
[tableStyle]="{ 'min-width': '50rem' }"
3535
[paginator]="true"
3636
[rows]="5"
3737
[rowsPerPageOptions]="[5, 10, 20]"

frontend/src/app/questions/questions.component.ts

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { DropdownModule } from 'primeng/dropdown';
1414
import { ProgressSpinnerModule } from 'primeng/progressspinner';
1515
import { Question } from './question.model';
1616
import { QuestionService } from '../../_services/question.service';
17-
import { forkJoin } from 'rxjs';
1817
import { HttpErrorResponse } from '@angular/common/http';
1918
import { QuestionDialogComponent } from './question-dialog.component';
2019
import { Column } from './column.model';
@@ -85,9 +84,7 @@ export class QuestionsComponent implements OnInit {
8584
message: 'Are you sure you want to delete the selected questions?',
8685
header: 'Delete Confirmation',
8786
icon: 'pi pi-exclamation-triangle',
88-
accept: () => {
89-
this.handleDeleteQuestionResponse();
90-
},
87+
accept: () => this.handleDeleteQuestionsResponse(),
9188
});
9289
}
9390

@@ -101,21 +98,15 @@ export class QuestionsComponent implements OnInit {
10198
};
10299
}
103100

104-
handleDeleteQuestionResponse() {
105-
const deleteRequests = this.selectedQuestions?.map(q => this.questionService.deleteQuestion(q.id));
106-
107-
forkJoin(deleteRequests!).subscribe({
101+
handleDeleteQuestionsResponse() {
102+
const ids = this.selectedQuestions?.map(q => q.id) || [];
103+
this.questionService.deleteQuestions(ids).subscribe({
108104
next: () => {
109-
// delete locally
110-
this.questions = this.questions?.filter(val => !this.selectedQuestions?.includes(val));
105+
this.questions = this.questions?.filter(q => !ids.includes(q.id));
111106
this.selectedQuestions = null;
112107
},
113-
error: (error: HttpErrorResponse) => {
114-
this.onErrorReceive('Some questions could not be deleted. ' + error.error.message);
115-
},
116-
complete: () => {
117-
this.onSuccessfulRequest('Question(s) Deleted');
118-
},
108+
error: (error: HttpErrorResponse) => this.onErrorReceive(error.error.message),
109+
complete: () => this.onSuccessfulRequest('Question(s) Deleted'),
119110
});
120111
}
121112

services/question/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,41 @@ curl -X DELETE http://localhost:8081/questions/21
422422
```
423423

424424
---
425+
426+
## Delete Questions
427+
428+
This endpoint allows the deletion of multiple questions by their question IDs.
429+
430+
- **HTTP Method**: `POST`
431+
- **Endpoint**: `/questions/delete`
432+
433+
### Parameters:
434+
435+
- `ids` (Required) - An array of integers representing the IDs of the questions to delete, e.g. `[1, 2, 3]`.
436+
437+
### Responses:
438+
439+
| Response Code | Explanation |
440+
|-----------------------------|------------------------------------------------------|
441+
| 200 (OK) | Success, the question is deleted successfully. |
442+
| 400 (Bad Request) | The `ids` parameter was not specified or is invalid. |
443+
| 404 (Not Found) | A question with the specified id not found. |
444+
| 500 (Internal Server Error) | Unexpected error in the database or server. |
445+
446+
### Command Line Example:
447+
448+
```
449+
curl -X POST http://localhost:8081/questions/delete -H "Content-Type: application/json" -d '{"ids": [21, 22]}'
450+
```
451+
452+
### Example of Response Body for Success:
453+
454+
```json
455+
{
456+
"status": "Success",
457+
"message": "Questions deleted successfully",
458+
"data": null
459+
}
460+
```
461+
462+
---

services/question/src/controllers/questionController.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,32 @@ export const deleteQuestion = async (req: Request, res: Response) => {
246246
handleError(res, 'Failed to delete question');
247247
}
248248
};
249+
250+
/**
251+
* This endpoint allows deletion of multiple questions by the question ID.
252+
* @param req
253+
* @param res
254+
*/
255+
export const deleteQuestions = async (req: Request, res: Response) => {
256+
const { ids } = req.body;
257+
258+
if (!ids || !Array.isArray(ids)) {
259+
return handleBadRequest(res, 'IDs are missing or not specified as an array');
260+
}
261+
262+
const deletedIDs = ids.map((id: any) => parseInt(id, 10));
263+
if (deletedIDs.some((id: any) => isNaN(id))) {
264+
return handleBadRequest(res, 'Invalid question ID');
265+
}
266+
267+
try {
268+
const count = await Question.countDocuments({ id: { $in: deletedIDs } });
269+
270+
if (count !== ids.length) return handleNotFound(res, 'Question not found');
271+
await Question.deleteMany({ id: { $in: deletedIDs } });
272+
handleSuccess(res, 200, 'Questions deleted successfully', null);
273+
} catch (error) {
274+
console.log('Error in deleteQuestions:', error);
275+
handleError(res, 'Failed to delete questions');
276+
}
277+
};

services/question/src/routes/questionRoutes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
addQuestion,
88
deleteQuestion,
99
updateQuestion,
10+
deleteQuestions,
1011
} from '../controllers/questionController';
1112

1213
/**
@@ -38,4 +39,9 @@ questionRouter.put('/:id', updateQuestion);
3839
*/
3940
questionRouter.delete('/:id', deleteQuestion);
4041

42+
/**
43+
* Delete questions from the database.
44+
*/
45+
questionRouter.post('/delete', deleteQuestions);
46+
4147
export default questionRouter;

0 commit comments

Comments
 (0)