From 59b906bb87d24789d0c3e64fad993a26f18765dd Mon Sep 17 00:00:00 2001 From: Tat Uyen Tam Date: Fri, 20 Mar 2026 15:24:20 +0700 Subject: [PATCH 1/4] docs: add openapi docs and swagger ui --- pom.xml | 7 +++++++ .../be08/smart_notes/common/SecurityConstants.java | 4 +++- .../controller/AIGenerationController.java | 8 +++++++- .../smart_notes/controller/AttemptController.java | 10 ++++++++++ .../controller/AuthenticationController.java | 7 +++++++ .../smart_notes/controller/DocumentController.java | 5 +++++ .../smart_notes/controller/FlashcardController.java | 7 +++++++ .../controller/FlashcardSetController.java | 9 +++++++++ .../be08/smart_notes/controller/NoteController.java | 8 ++++++++ .../be08/smart_notes/controller/QuizController.java | 12 +++++++++--- .../smart_notes/controller/QuizSetController.java | 13 ++++++++++++- .../be08/smart_notes/controller/UserController.java | 4 ++++ src/main/resources/application-dev.properties | 3 +++ 13 files changed, 91 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index bbf615a..20115cc 100644 --- a/pom.xml +++ b/pom.xml @@ -209,6 +209,13 @@ jsonschema-module-jakarta-validation 4.31.1 + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.5 + diff --git a/src/main/java/com/be08/smart_notes/common/SecurityConstants.java b/src/main/java/com/be08/smart_notes/common/SecurityConstants.java index 6784ece..6ed0f5f 100644 --- a/src/main/java/com/be08/smart_notes/common/SecurityConstants.java +++ b/src/main/java/com/be08/smart_notes/common/SecurityConstants.java @@ -4,6 +4,8 @@ public class SecurityConstants { public static final String[] PUBLIC_ENDPOINTS = { "/api/auth/register", "/api/auth/login", - "/api/auth/refresh" + "/api/auth/refresh", + "/api-docs", + "/swagger-ui/*", }; } diff --git a/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java b/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java index c6c70ba..812211e 100644 --- a/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java +++ b/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java @@ -6,6 +6,8 @@ import com.be08.smart_notes.dto.response.QuizSetResponse; import com.be08.smart_notes.validation.group.MultipleDocument; import com.be08.smart_notes.validation.group.SingleDocument; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -20,11 +22,13 @@ @RequestMapping("/api/ai/generation") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "AI Generation Features", description = "Operation for AI-related features") public class AIGenerationController { QuizGenerationService quizGenerationService; @GetMapping("/quiz-sets/sample") - public ResponseEntity generateSampleQuizSet() { + @Operation(summary = "Generate sample quiz set") + public ResponseEntity generateSampleQuiz() { QuizResponse quizResponse = quizGenerationService.generateSampleQuiz(); ApiResponse apiResponse = ApiResponse.builder() .message("Sample quiz set successfully generated") @@ -34,6 +38,7 @@ public ResponseEntity generateSampleQuizSet() { } @PostMapping("/quiz-sets/default") + @Operation(summary = "Generate single quiz based given request, stored in default quiz set") public ResponseEntity generateQuiz(@Validated(SingleDocument.class) @RequestBody QuizGenerationRequest quizGenerationRequest) { QuizResponse quizSetResponse = quizGenerationService.generateQuiz(quizGenerationRequest); ApiResponse apiResponse = ApiResponse.builder() @@ -44,6 +49,7 @@ public ResponseEntity generateQuiz(@Validated(SingleDocument.class) @Req } @PostMapping("/quiz-sets") + @Operation(summary = "Generate multiple quizzes based given request, grouped in new quiz set") public ResponseEntity generateQuizSet(@Validated(MultipleDocument.class) @RequestBody QuizGenerationRequest quizGenerationRequest) { QuizSetResponse quizSetResponse = quizGenerationService.generateQuizSet(quizGenerationRequest); ApiResponse apiResponse = ApiResponse.builder() diff --git a/src/main/java/com/be08/smart_notes/controller/AttemptController.java b/src/main/java/com/be08/smart_notes/controller/AttemptController.java index 3b578cc..adf625b 100644 --- a/src/main/java/com/be08/smart_notes/controller/AttemptController.java +++ b/src/main/java/com/be08/smart_notes/controller/AttemptController.java @@ -8,6 +8,8 @@ import com.be08.smart_notes.dto.view.AttemptView; import com.be08.smart_notes.service.AttemptService; import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -21,11 +23,13 @@ @RequestMapping("/api/quizzes") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "Attempts", description = "Operation for quiz attempts") public class AttemptController { AttemptService attemptService; @PostMapping("/{quizId}/attempts") @JsonView(AttemptView.Detail.class) + @Operation(summary = "Create new attempt for target quiz") public ResponseEntity createAttempt(@PathVariable int quizId) { AttemptResponse attemptResponse = attemptService.createNewAttempt(quizId); ApiResponse apiResponse = ApiResponse.builder() @@ -37,6 +41,7 @@ public ResponseEntity createAttempt(@PathVariable int quizId) { @GetMapping("/{quizId}/attempts") @JsonView(AttemptView.Basic.class) + @Operation(summary = "Get all attempts by page") public ResponseEntity getAllAttemptsForQuiz(@PathVariable int quizId, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { @@ -50,6 +55,7 @@ public ResponseEntity getAllAttemptsForQuiz(@PathVariable int quizId, @GetMapping("/{quizId}/attempts/{attemptId}") @JsonView(AttemptView.Detail.class) + @Operation(summary = "Get attempt and its recorded answers") public ResponseEntity getAttempt(@PathVariable int quizId, @PathVariable int attemptId) { AttemptResponse attemptResponseList = attemptService.getAttemptByIdAndQuizId(quizId, attemptId); ApiResponse apiResponse = ApiResponse.builder() @@ -61,6 +67,7 @@ public ResponseEntity getAttempt(@PathVariable int quizId, @PathVariable @GetMapping("/{quizId}/attempts/{attemptId}/answer") @JsonView(AttemptView.Answer.class) + @Operation(summary = "Get attempt with its recorded answers and correct answers") public ResponseEntity getAttemptResult(@PathVariable int quizId, @PathVariable int attemptId) { AttemptResponse attemptResponseList = attemptService.getAttemptByIdAndQuizId(quizId, attemptId); ApiResponse apiResponse = ApiResponse.builder() @@ -72,6 +79,7 @@ public ResponseEntity getAttemptResult(@PathVariable int quizId, @PathVa @PostMapping("/{quizId}/attempts/{attemptId}/answer") @JsonView(AttemptView.Answer.class) + @Operation(summary = "Finish attempt and calculate result") public ResponseEntity finishAttempt(@PathVariable int quizId, @PathVariable int attemptId) { AttemptResponse attemptResponseList = attemptService.calculateAttemptResult(quizId, attemptId); ApiResponse apiResponse = ApiResponse.builder() @@ -83,6 +91,7 @@ public ResponseEntity finishAttempt(@PathVariable int quizId, @PathVaria @PatchMapping("/{quizId}/attempts/{attemptId}") @JsonView(AttemptView.Answer.class) + @Operation(summary = "Update attempt detail and/or record answer") public ResponseEntity updateAttemptDetail(@PathVariable int quizId, @PathVariable int attemptId, @Valid @RequestBody AttemptDetailUpdateRequest request) { AttemptResponse.Detail attemptDetailResponse = attemptService.updateAttemptDetail(quizId, attemptId, request); ApiResponse apiResponse = ApiResponse.builder() @@ -93,6 +102,7 @@ public ResponseEntity updateAttemptDetail(@PathVariable int quizId, @Pat } @DeleteMapping("/{quizId}/attempts/{attemptId}") + @Operation(summary = "Delete attempt") public ResponseEntity deleteAttempt(@PathVariable int quizId, @PathVariable int attemptId) { attemptService.deleteAttemptByIdAndQuizId(quizId, attemptId); ApiResponse apiResponse = ApiResponse.builder() diff --git a/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java b/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java index 4aabdb0..ac361e5 100644 --- a/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java +++ b/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java @@ -8,6 +8,8 @@ import com.be08.smart_notes.dto.response.UserResponse; import com.be08.smart_notes.service.AuthenticationService; import com.be08.smart_notes.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -22,6 +24,7 @@ @RequestMapping("/api/auth") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "Authentication", description = "Operation for authentication features") public class AuthenticationController { AuthenticationService authenticationService; UserService userService; @@ -32,6 +35,7 @@ public class AuthenticationController { * @return ApiResponse containing UserResponse */ @PostMapping("/register") + @Operation(summary = "Register new user") ApiResponse register(@RequestBody @Valid UserCreationRequest request){ UserResponse response = userService.createUser(request); return ApiResponse.builder() @@ -46,6 +50,7 @@ ApiResponse register(@RequestBody @Valid UserCreationRequest reque * @return ApiResponse containing AuthenticationResponse */ @PostMapping("/login") + @Operation(summary = "Login with email and password") ApiResponse login(@RequestBody @Valid LoginRequest request){ AuthenticationResponse response = authenticationService.login(request); return ApiResponse.builder() @@ -60,6 +65,7 @@ ApiResponse login(@RequestBody @Valid LoginRequest reque * @return ApiResponse containing new AuthenticationResponse */ @PostMapping("/refresh") + @Operation(summary = "Refresh JWT Token") ApiResponse refreshToken(@RequestBody @Valid RefreshTokenRequest request){ AuthenticationResponse response = authenticationService.refreshToken(request); return ApiResponse.builder() @@ -75,6 +81,7 @@ ApiResponse refreshToken(@RequestBody @Valid RefreshToke * @return ApiResponse with logout confirmation */ @PostMapping("/logout") + @Operation(summary = "Logout and blacklist current token") ApiResponse logout(Authentication authentication){ authenticationService.logout(authentication); diff --git a/src/main/java/com/be08/smart_notes/controller/DocumentController.java b/src/main/java/com/be08/smart_notes/controller/DocumentController.java index 1dc6096..ce6a6c6 100644 --- a/src/main/java/com/be08/smart_notes/controller/DocumentController.java +++ b/src/main/java/com/be08/smart_notes/controller/DocumentController.java @@ -2,6 +2,8 @@ import com.be08.smart_notes.common.DefaultConstants; import com.be08.smart_notes.dto.response.PageResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -17,10 +19,12 @@ @RequestMapping("/api/documents") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "Documents", description = "Operation for documents (notes and PDFs)") public class DocumentController { DocumentService documentService; @GetMapping + @Operation(summary = "Get all documents by page") public ResponseEntity getAllDocuments( @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { @@ -33,6 +37,7 @@ public ResponseEntity getAllDocuments( } @DeleteMapping("/{id}") + @Operation(summary = "Delete document") public ResponseEntity deleteDocument(@PathVariable int id) { documentService.deleteDocument(id); ApiResponse apiResponse = ApiResponse.builder() diff --git a/src/main/java/com/be08/smart_notes/controller/FlashcardController.java b/src/main/java/com/be08/smart_notes/controller/FlashcardController.java index 95c36a3..4e38419 100644 --- a/src/main/java/com/be08/smart_notes/controller/FlashcardController.java +++ b/src/main/java/com/be08/smart_notes/controller/FlashcardController.java @@ -4,6 +4,8 @@ import com.be08.smart_notes.dto.response.ApiResponse; import com.be08.smart_notes.dto.response.FlashcardResponse; import com.be08.smart_notes.service.FlashcardService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -14,10 +16,12 @@ @RequestMapping("/api/flashcards") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "Flashcards", description = "Operation for flashcards") public class FlashcardController { FlashcardService flashcardService; @PostMapping + @Operation(summary = "Create new flashcard") public ApiResponse createFlashcard(@RequestBody FlashcardCreationRequest request){ FlashcardResponse response = flashcardService.createFlashcard(request); @@ -28,6 +32,7 @@ public ApiResponse createFlashcard(@RequestBody FlashcardCrea } @GetMapping("/{flashcardId}") + @Operation(summary = "Create flashcard") public ApiResponse getFlashcard(@PathVariable int flashcardId){ FlashcardResponse response = flashcardService.getFlashcardById(flashcardId); @@ -38,6 +43,7 @@ public ApiResponse getFlashcard(@PathVariable int flashcardId } @PutMapping("/{flashcardId}") + @Operation(summary = "Update flashcard") public ApiResponse updateFlashcard(@PathVariable int flashcardId, @RequestBody @Valid FlashcardCreationRequest request){ FlashcardResponse response = flashcardService.updateFlashcard(flashcardId, request); @@ -48,6 +54,7 @@ public ApiResponse updateFlashcard(@PathVariable int flashcar } @DeleteMapping("/{flashcardId}") + @Operation(summary = "Delete flashcard") public ApiResponse deleteFlashcard(@PathVariable int flashcardId){ flashcardService.deleteFlashcard(flashcardId); diff --git a/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java b/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java index b6fc92c..e2e6a70 100644 --- a/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java +++ b/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java @@ -6,6 +6,8 @@ import com.be08.smart_notes.dto.response.FlashcardSetResponse; import com.be08.smart_notes.service.FlashcardService; import com.be08.smart_notes.service.FlashcardSetService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -18,6 +20,7 @@ @RequestMapping("/api/flashcard-sets") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "Flashcard Sets", description = "Operation for flashcard sets") public class FlashcardSetController { FlashcardSetService flashcardSetService; FlashcardService flashcardService; @@ -28,6 +31,7 @@ public class FlashcardSetController { * @return ApiResponse containing FlashcardSetResponse */ @PostMapping + @Operation(summary = "Create flashcard set") public ApiResponse createFlashcardSet(@RequestBody @Valid FlashcardSetCreationRequest request){ FlashcardSetResponse response = flashcardSetService.createFlashcardSet(request); @@ -42,6 +46,7 @@ public ApiResponse createFlashcardSet(@RequestBody @Valid * @return ApiResponse containing list of FlashcardSetResponse */ @GetMapping + @Operation(summary = "Get all flashcard sets") public ApiResponse> getAllFlashcardSets(){ List response = flashcardSetService.getAllFlashcardSets(); @@ -57,6 +62,7 @@ public ApiResponse> getAllFlashcardSets(){ * @return ApiResponse containing FlashcardSetResponse */ @GetMapping("/{flashcardSetId}") + @Operation(summary = "Get flashcard set") public ApiResponse getFlashcardSet(@PathVariable int flashcardSetId){ FlashcardSetResponse response = flashcardSetService.getFlashcardSet(flashcardSetId); @@ -72,6 +78,7 @@ public ApiResponse getFlashcardSet(@PathVariable int flash * @return ApiResponse containing list of FlashcardResponse */ @GetMapping("/{flashcardSetId}/flashcards") + @Operation(summary = "Get all flashcards from current set") public ApiResponse> getFlashcardsBySet(@PathVariable int flashcardSetId){ List response = flashcardService.getFlashcardsBySetId(flashcardSetId); @@ -88,6 +95,7 @@ public ApiResponse> getFlashcardsBySet(@PathVariable int * @return ApiResponse containing updated FlashcardSetResponse */ @PutMapping("/{flashcardSetId}") + @Operation(summary = "Update flashcard set") public ApiResponse updateFlashcardSet(@PathVariable int flashcardSetId, @RequestBody @Valid FlashcardSetCreationRequest request){ FlashcardSetResponse response = flashcardSetService.updateFlashcardSet(flashcardSetId, request); @@ -103,6 +111,7 @@ public ApiResponse updateFlashcardSet(@PathVariable int fl * @return ApiResponse with deletion confirmation */ @DeleteMapping("/{flashcardSetId}") + @Operation(summary = "Delete flashcard set") public ApiResponse deleteFlashcardSet(@PathVariable int flashcardSetId){ flashcardSetService.deleteFlashcardSet(flashcardSetId); diff --git a/src/main/java/com/be08/smart_notes/controller/NoteController.java b/src/main/java/com/be08/smart_notes/controller/NoteController.java index 7771240..49a695c 100644 --- a/src/main/java/com/be08/smart_notes/controller/NoteController.java +++ b/src/main/java/com/be08/smart_notes/controller/NoteController.java @@ -4,6 +4,8 @@ import com.be08.smart_notes.dto.filter.BasicFilterDTO; import com.be08.smart_notes.dto.response.NoteResponse; import com.be08.smart_notes.dto.response.PageResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -20,10 +22,12 @@ @RequestMapping("/api/documents/notes") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "Notes", description = "All operations for notes") public class NoteController { NoteService noteService; @PostMapping + @Operation(summary = "Create new note") public ResponseEntity createNote(@RequestBody @Valid NoteUpsertRequest noteCreationRequest) { NoteResponse createdNote = noteService.createNote(noteCreationRequest); ApiResponse apiResponse = ApiResponse.builder() @@ -34,6 +38,7 @@ public ResponseEntity createNote(@RequestBody @Valid NoteUpsertRequest n } @GetMapping("/{id}") + @Operation(summary = "Get note") public ResponseEntity getNote(@PathVariable int id) { NoteResponse note = noteService.getNote(id); ApiResponse apiResponse = ApiResponse.builder() @@ -44,6 +49,7 @@ public ResponseEntity getNote(@PathVariable int id) { } @GetMapping + @Operation(summary = "Get all notes by page") public ResponseEntity getAllNotes( @ModelAttribute BasicFilterDTO filterDTO, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, @@ -59,6 +65,7 @@ public ResponseEntity getAllNotes( } @PatchMapping("/{id}") + @Operation(summary = "Update note") public ResponseEntity updateNote(@PathVariable int id, @RequestBody @Valid NoteUpsertRequest noteUpdateRequest) { NoteResponse note = noteService.updateNote(id, noteUpdateRequest); ApiResponse apiResponse = ApiResponse.builder() @@ -69,6 +76,7 @@ public ResponseEntity updateNote(@PathVariable int id, @RequestBody @Val } @DeleteMapping("/{id}") + @Operation(summary = "Delete note") public ResponseEntity deleteNote(@PathVariable int id) { noteService.deleteNote(id); ApiResponse apiResponse = ApiResponse.builder() diff --git a/src/main/java/com/be08/smart_notes/controller/QuizController.java b/src/main/java/com/be08/smart_notes/controller/QuizController.java index 483c6a1..5f5b329 100644 --- a/src/main/java/com/be08/smart_notes/controller/QuizController.java +++ b/src/main/java/com/be08/smart_notes/controller/QuizController.java @@ -11,6 +11,8 @@ import com.be08.smart_notes.validation.group.OnCreate; import com.be08.smart_notes.validation.group.OnUpdate; import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -19,17 +21,17 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequestMapping("/api/quizzes") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "Quizzes", description = "All operations for quizzes") public class QuizController { QuizService quizService; @PostMapping @JsonView(QuizView.Detail.class) + @Operation(summary = "Create new quiz with associated questions") public ResponseEntity createQuiz(@RequestBody @Validated(OnCreate.class) QuizUpsertDTO request) { QuizResponse quizResponse = quizService.createQuiz(request); ApiResponse apiResponse = ApiResponse.builder() @@ -41,6 +43,7 @@ public ResponseEntity createQuiz(@RequestBody @Validated(OnCreate.class) @GetMapping @JsonView(QuizView.Basic.class) + @Operation(summary = "Get all quizzes by page") public ResponseEntity getAllQuizzes( @ModelAttribute QuizFilterDTO filterDTO, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, @@ -58,6 +61,7 @@ public ResponseEntity getAllQuizzes( @GetMapping("/{id}") @JsonView(QuizView.Detail.class) + @Operation(summary = "Get quiz and all of its questions") public ResponseEntity getQuiz(@PathVariable int id) { QuizResponse quizResponse = quizService.getQuizById(id); ApiResponse apiResponse = ApiResponse.builder() @@ -68,7 +72,8 @@ public ResponseEntity getQuiz(@PathVariable int id) { } @PatchMapping("/{id}") - @JsonView(QuizView.Detail.class) + @JsonView(QuizView.Basic.class) + @Operation(summary = "Update quiz information") public ResponseEntity updateQuiz(@PathVariable int id, @Validated(OnUpdate.class) @RequestBody QuizUpsertDTO request) { QuizResponse quizResponse = quizService.updateQuiz(id, request); ApiResponse apiResponse = ApiResponse.builder() @@ -79,6 +84,7 @@ public ResponseEntity updateQuiz(@PathVariable int id, @Validated(OnUpda } @DeleteMapping("/{id}") + @Operation(summary = "Delete quiz") public ResponseEntity deleteQuiz(@PathVariable int id) { quizService.deleteQuizById(id); ApiResponse apiResponse = ApiResponse.builder() diff --git a/src/main/java/com/be08/smart_notes/controller/QuizSetController.java b/src/main/java/com/be08/smart_notes/controller/QuizSetController.java index 0b13ff0..ce911e2 100644 --- a/src/main/java/com/be08/smart_notes/controller/QuizSetController.java +++ b/src/main/java/com/be08/smart_notes/controller/QuizSetController.java @@ -10,6 +10,8 @@ import com.be08.smart_notes.enums.OriginType; import com.be08.smart_notes.service.QuizSetService; import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -22,11 +24,13 @@ @RequestMapping("/api/quiz-sets") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "Quiz Sets", description = "All operations for quiz sets") public class QuizSetController { QuizSetService quizSetService; @PostMapping @JsonView(QuizView.Detail.class) + @Operation(summary = "Create new quiz set") public ResponseEntity createQuizSet(@RequestBody @Valid QuizSetUpsertRequest request) { QuizSetResponse quizSetResponse = quizSetService.createQuizSet(request, OriginType.USER); ApiResponse apiResponse = ApiResponse.builder() @@ -38,6 +42,7 @@ public ResponseEntity createQuizSet(@RequestBody @Valid QuizSetUpsertReq @GetMapping @JsonView(QuizView.Basic.class) + @Operation(summary = "Get all quiz sets") public ResponseEntity getAllQuizSets( @ModelAttribute BasicFilterDTO filterDTO, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, @@ -54,6 +59,7 @@ public ResponseEntity getAllQuizSets( @GetMapping("/default") @JsonView(QuizView.Detail.class) + @Operation(summary = "Get default quiz set and its quizzes") public ResponseEntity getDefaultQuizSet() { QuizSetResponse quizSetResponse = quizSetService.getDefaultSet(); ApiResponse apiResponse = ApiResponse.builder() @@ -64,7 +70,8 @@ public ResponseEntity getDefaultQuizSet() { } @GetMapping("/{id}") - @JsonView(QuizView.Detail.class) + @JsonView(QuizView.Basic.class) + @Operation(summary = "Get quiz set") public ResponseEntity getQuizSet(@PathVariable int id) { QuizSetResponse quizSetResponse = quizSetService.getQuizSetById(id, false); ApiResponse apiResponse = ApiResponse.builder() @@ -76,6 +83,7 @@ public ResponseEntity getQuizSet(@PathVariable int id) { @GetMapping("/{id}/quizzes") @JsonView(QuizView.Detail.class) + @Operation(summary = "Get quiz set and its quizzes") public ResponseEntity getQuizSetWithQuizzes(@PathVariable int id) { QuizSetResponse quizSetResponse = quizSetService.getQuizSetById(id, true); ApiResponse apiResponse = ApiResponse.builder() @@ -87,6 +95,7 @@ public ResponseEntity getQuizSetWithQuizzes(@PathVariable int id) { @PatchMapping("/{id}") @JsonView(QuizView.Detail.class) + @Operation(summary = "Update quiz set information") public ResponseEntity updateQuizSet(@PathVariable int id, @RequestBody QuizSetUpsertRequest request) { QuizSetResponse quizSetResponse = quizSetService.updateQuizSet(id, request); ApiResponse apiResponse = ApiResponse.builder() @@ -97,6 +106,7 @@ public ResponseEntity updateQuizSet(@PathVariable int id, @RequestBody Q } @DeleteMapping + @Operation(summary = "Delete all quiz sets") public ResponseEntity deleteAllQuizSet() { quizSetService.deleteAllQuizSet(); ApiResponse apiResponse = ApiResponse.builder() @@ -106,6 +116,7 @@ public ResponseEntity deleteAllQuizSet() { } @DeleteMapping("/{id}") + @Operation(summary = "Delete quiz set") public ResponseEntity deleteQuizSet(@PathVariable int id) { quizSetService.deleteQuizSetById(id); ApiResponse apiResponse = ApiResponse.builder() diff --git a/src/main/java/com/be08/smart_notes/controller/UserController.java b/src/main/java/com/be08/smart_notes/controller/UserController.java index b77239a..4121977 100644 --- a/src/main/java/com/be08/smart_notes/controller/UserController.java +++ b/src/main/java/com/be08/smart_notes/controller/UserController.java @@ -3,6 +3,8 @@ import com.be08.smart_notes.dto.response.ApiResponse; import com.be08.smart_notes.dto.response.UserResponse; import com.be08.smart_notes.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; @@ -14,6 +16,7 @@ @RequestMapping("/api/users") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Tag(name = "User", description = "All operations for user-related features") public class UserController { UserService userService; @@ -22,6 +25,7 @@ public class UserController { * @return ApiResponse containing UserResponse */ @GetMapping("/me") + @Operation(summary = "Get user detail") public ApiResponse getMe(){ UserResponse response = userService.getMe(); diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 2826672..ce3a5b9 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,3 +1,6 @@ +springdoc.api-docs.path=/api-docs +springdoc.swagger-ui.tagsSorter=alpha + # Database Configuration for Development (Local MySQL) spring.datasource.url=jdbc:mysql://localhost:${DB_PORT}/${DB_NAME} spring.datasource.username=${DB_USERNAME} From 5cb4d476ebc218f056b4ba8c87984ceaa682e61e Mon Sep 17 00:00:00 2001 From: Tat Uyen Tam Date: Fri, 20 Mar 2026 20:24:29 +0700 Subject: [PATCH 2/4] docs: add bearer authentication for swagger docs --- .../smart_notes/common/SecurityConstants.java | 4 +- .../config/OpenAPIAuthConfiguration.java | 15 +++++++ .../controller/AIGenerationController.java | 21 ++++----- .../controller/AttemptController.java | 41 +++++++---------- .../controller/AuthenticationController.java | 2 + .../controller/DocumentController.java | 14 +++--- .../controller/FlashcardController.java | 2 + .../controller/FlashcardSetController.java | 2 + .../controller/NoteController.java | 29 +++++------- .../controller/QuizController.java | 29 +++++------- .../controller/QuizSetController.java | 44 ++++++++----------- .../controller/UserController.java | 2 + .../smart_notes/dto/filter/QuizFilterDTO.java | 2 +- src/main/resources/application-dev.properties | 1 + 14 files changed, 102 insertions(+), 106 deletions(-) create mode 100644 src/main/java/com/be08/smart_notes/config/OpenAPIAuthConfiguration.java diff --git a/src/main/java/com/be08/smart_notes/common/SecurityConstants.java b/src/main/java/com/be08/smart_notes/common/SecurityConstants.java index 6ed0f5f..0713673 100644 --- a/src/main/java/com/be08/smart_notes/common/SecurityConstants.java +++ b/src/main/java/com/be08/smart_notes/common/SecurityConstants.java @@ -6,6 +6,8 @@ public class SecurityConstants { "/api/auth/login", "/api/auth/refresh", "/api-docs", - "/swagger-ui/*", + "/api-docs/**", + "/swagger-ui/**", + "/swagger-ui.html" }; } diff --git a/src/main/java/com/be08/smart_notes/config/OpenAPIAuthConfiguration.java b/src/main/java/com/be08/smart_notes/config/OpenAPIAuthConfiguration.java new file mode 100644 index 0000000..a842a56 --- /dev/null +++ b/src/main/java/com/be08/smart_notes/config/OpenAPIAuthConfiguration.java @@ -0,0 +1,15 @@ +package com.be08.smart_notes.config; + +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import org.springframework.context.annotation.Configuration; + +@Configuration +@SecurityScheme( + name = "Bearer Authentication", + type = SecuritySchemeType.HTTP, + bearerFormat = "JWT", + scheme = "bearer" +) +public class OpenAPIAuthConfiguration { +} diff --git a/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java b/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java index 812211e..a3733c6 100644 --- a/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java +++ b/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java @@ -7,12 +7,11 @@ import com.be08.smart_notes.validation.group.MultipleDocument; import com.be08.smart_notes.validation.group.SingleDocument; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -22,40 +21,38 @@ @RequestMapping("/api/ai/generation") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -@Tag(name = "AI Generation Features", description = "Operation for AI-related features") +@Tag(name = "AI", description = "Operation for AI-related features") +@SecurityRequirement(name = "Bearer Authentication") public class AIGenerationController { QuizGenerationService quizGenerationService; @GetMapping("/quiz-sets/sample") @Operation(summary = "Generate sample quiz set") - public ResponseEntity generateSampleQuiz() { + public ApiResponse generateSampleQuiz() { QuizResponse quizResponse = quizGenerationService.generateSampleQuiz(); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Sample quiz set successfully generated") .data(quizResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @PostMapping("/quiz-sets/default") @Operation(summary = "Generate single quiz based given request, stored in default quiz set") - public ResponseEntity generateQuiz(@Validated(SingleDocument.class) @RequestBody QuizGenerationRequest quizGenerationRequest) { + public ApiResponse generateQuiz(@Validated(SingleDocument.class) @RequestBody QuizGenerationRequest quizGenerationRequest) { QuizResponse quizSetResponse = quizGenerationService.generateQuiz(quizGenerationRequest); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz successfully generated and added to default set") .data(quizSetResponse) .build(); - return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } @PostMapping("/quiz-sets") @Operation(summary = "Generate multiple quizzes based given request, grouped in new quiz set") - public ResponseEntity generateQuizSet(@Validated(MultipleDocument.class) @RequestBody QuizGenerationRequest quizGenerationRequest) { + public ApiResponse generateQuizSet(@Validated(MultipleDocument.class) @RequestBody QuizGenerationRequest quizGenerationRequest) { QuizSetResponse quizSetResponse = quizGenerationService.generateQuizSet(quizGenerationRequest); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz set successfully generated") .data(quizSetResponse) .build(); - return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } } diff --git a/src/main/java/com/be08/smart_notes/controller/AttemptController.java b/src/main/java/com/be08/smart_notes/controller/AttemptController.java index adf625b..0154756 100644 --- a/src/main/java/com/be08/smart_notes/controller/AttemptController.java +++ b/src/main/java/com/be08/smart_notes/controller/AttemptController.java @@ -9,13 +9,12 @@ import com.be08.smart_notes.service.AttemptService; import com.fasterxml.jackson.annotation.JsonView; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -23,91 +22,85 @@ @RequestMapping("/api/quizzes") @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -@Tag(name = "Attempts", description = "Operation for quiz attempts") +@Tag(name = "Quiz Attempts", description = "Operation for quiz attempts") +@SecurityRequirement(name = "Bearer Authentication") public class AttemptController { AttemptService attemptService; @PostMapping("/{quizId}/attempts") @JsonView(AttemptView.Detail.class) @Operation(summary = "Create new attempt for target quiz") - public ResponseEntity createAttempt(@PathVariable int quizId) { + public ApiResponse createAttempt(@PathVariable int quizId) { AttemptResponse attemptResponse = attemptService.createNewAttempt(quizId); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Attempt created successfully") .data(attemptResponse) .build(); - return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } @GetMapping("/{quizId}/attempts") @JsonView(AttemptView.Basic.class) @Operation(summary = "Get all attempts by page") - public ResponseEntity getAllAttemptsForQuiz(@PathVariable int quizId, + public ApiResponse> getAllAttemptsForQuiz(@PathVariable int quizId, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { PageResponse attemptResponseList = attemptService.getAllAttemptsByQuizId(quizId, page, size); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.>builder() .message("All attempts for quiz fetched successfully") .data(attemptResponseList) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @GetMapping("/{quizId}/attempts/{attemptId}") @JsonView(AttemptView.Detail.class) @Operation(summary = "Get attempt and its recorded answers") - public ResponseEntity getAttempt(@PathVariable int quizId, @PathVariable int attemptId) { + public ApiResponse getAttempt(@PathVariable int quizId, @PathVariable int attemptId) { AttemptResponse attemptResponseList = attemptService.getAttemptByIdAndQuizId(quizId, attemptId); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Attempt fetched successfully") .data(attemptResponseList) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @GetMapping("/{quizId}/attempts/{attemptId}/answer") @JsonView(AttemptView.Answer.class) @Operation(summary = "Get attempt with its recorded answers and correct answers") - public ResponseEntity getAttemptResult(@PathVariable int quizId, @PathVariable int attemptId) { + public ApiResponse getAttemptResult(@PathVariable int quizId, @PathVariable int attemptId) { AttemptResponse attemptResponseList = attemptService.getAttemptByIdAndQuizId(quizId, attemptId); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Attempt result fetched successfully") .data(attemptResponseList) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @PostMapping("/{quizId}/attempts/{attemptId}/answer") @JsonView(AttemptView.Answer.class) @Operation(summary = "Finish attempt and calculate result") - public ResponseEntity finishAttempt(@PathVariable int quizId, @PathVariable int attemptId) { + public ApiResponse finishAttempt(@PathVariable int quizId, @PathVariable int attemptId) { AttemptResponse attemptResponseList = attemptService.calculateAttemptResult(quizId, attemptId); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Attempt result calculated successfully") .data(attemptResponseList) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @PatchMapping("/{quizId}/attempts/{attemptId}") @JsonView(AttemptView.Answer.class) @Operation(summary = "Update attempt detail and/or record answer") - public ResponseEntity updateAttemptDetail(@PathVariable int quizId, @PathVariable int attemptId, @Valid @RequestBody AttemptDetailUpdateRequest request) { + public ApiResponse updateAttemptDetail(@PathVariable int quizId, @PathVariable int attemptId, @Valid @RequestBody AttemptDetailUpdateRequest request) { AttemptResponse.Detail attemptDetailResponse = attemptService.updateAttemptDetail(quizId, attemptId, request); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Attempt detail updated successfully") .data(attemptDetailResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @DeleteMapping("/{quizId}/attempts/{attemptId}") @Operation(summary = "Delete attempt") - public ResponseEntity deleteAttempt(@PathVariable int quizId, @PathVariable int attemptId) { + public ApiResponse deleteAttempt(@PathVariable int quizId, @PathVariable int attemptId) { attemptService.deleteAttemptByIdAndQuizId(quizId, attemptId); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Attempt deleted successfully") .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } } diff --git a/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java b/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java index ac361e5..be9d1b3 100644 --- a/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java +++ b/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java @@ -9,6 +9,7 @@ import com.be08.smart_notes.service.AuthenticationService; import com.be08.smart_notes.service.UserService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; @@ -82,6 +83,7 @@ ApiResponse refreshToken(@RequestBody @Valid RefreshToke */ @PostMapping("/logout") @Operation(summary = "Logout and blacklist current token") + @SecurityRequirement(name = "Bearer Authentication") ApiResponse logout(Authentication authentication){ authenticationService.logout(authentication); diff --git a/src/main/java/com/be08/smart_notes/controller/DocumentController.java b/src/main/java/com/be08/smart_notes/controller/DocumentController.java index ce6a6c6..84073ce 100644 --- a/src/main/java/com/be08/smart_notes/controller/DocumentController.java +++ b/src/main/java/com/be08/smart_notes/controller/DocumentController.java @@ -3,12 +3,11 @@ import com.be08.smart_notes.common.DefaultConstants; import com.be08.smart_notes.dto.response.PageResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import com.be08.smart_notes.dto.response.ApiResponse; @@ -20,29 +19,28 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Tag(name = "Documents", description = "Operation for documents (notes and PDFs)") +@SecurityRequirement(name = "Bearer Authentication") public class DocumentController { DocumentService documentService; @GetMapping @Operation(summary = "Get all documents by page") - public ResponseEntity getAllDocuments( + public ApiResponse> getAllDocuments( @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { PageResponse documentList = documentService.getAllDocuments(page, size); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.>builder() .message("All document fetched successfully") .data(documentList) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @DeleteMapping("/{id}") @Operation(summary = "Delete document") - public ResponseEntity deleteDocument(@PathVariable int id) { + public ApiResponse deleteDocument(@PathVariable int id) { documentService.deleteDocument(id); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Document deleted successfully") .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } } diff --git a/src/main/java/com/be08/smart_notes/controller/FlashcardController.java b/src/main/java/com/be08/smart_notes/controller/FlashcardController.java index 4e38419..22fbe7b 100644 --- a/src/main/java/com/be08/smart_notes/controller/FlashcardController.java +++ b/src/main/java/com/be08/smart_notes/controller/FlashcardController.java @@ -5,6 +5,7 @@ import com.be08.smart_notes.dto.response.FlashcardResponse; import com.be08.smart_notes.service.FlashcardService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; @@ -17,6 +18,7 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Tag(name = "Flashcards", description = "Operation for flashcards") +@SecurityRequirement(name = "Bearer Authentication") public class FlashcardController { FlashcardService flashcardService; diff --git a/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java b/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java index e2e6a70..1617e7a 100644 --- a/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java +++ b/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java @@ -7,6 +7,7 @@ import com.be08.smart_notes.service.FlashcardService; import com.be08.smart_notes.service.FlashcardSetService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; @@ -21,6 +22,7 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Tag(name = "Flashcard Sets", description = "Operation for flashcard sets") +@SecurityRequirement(name = "Bearer Authentication") public class FlashcardSetController { FlashcardSetService flashcardSetService; FlashcardService flashcardService; diff --git a/src/main/java/com/be08/smart_notes/controller/NoteController.java b/src/main/java/com/be08/smart_notes/controller/NoteController.java index 49a695c..2d0be24 100644 --- a/src/main/java/com/be08/smart_notes/controller/NoteController.java +++ b/src/main/java/com/be08/smart_notes/controller/NoteController.java @@ -5,13 +5,12 @@ import com.be08.smart_notes.dto.response.NoteResponse; import com.be08.smart_notes.dto.response.PageResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import com.be08.smart_notes.dto.request.NoteUpsertRequest; @@ -23,65 +22,61 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Tag(name = "Notes", description = "All operations for notes") +@SecurityRequirement(name = "Bearer Authentication") public class NoteController { NoteService noteService; @PostMapping @Operation(summary = "Create new note") - public ResponseEntity createNote(@RequestBody @Valid NoteUpsertRequest noteCreationRequest) { + public ApiResponse createNote(@RequestBody @Valid NoteUpsertRequest noteCreationRequest) { NoteResponse createdNote = noteService.createNote(noteCreationRequest); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Note created successfully") .data(createdNote) .build(); - return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } @GetMapping("/{id}") @Operation(summary = "Get note") - public ResponseEntity getNote(@PathVariable int id) { + public ApiResponse getNote(@PathVariable int id) { NoteResponse note = noteService.getNote(id); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Note fetched successfully") .data(note) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @GetMapping @Operation(summary = "Get all notes by page") - public ResponseEntity getAllNotes( + public ApiResponse> getAllNotes( @ModelAttribute BasicFilterDTO filterDTO, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_ORDER) String sortOrder, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { PageResponse pageResponse = noteService.getAllNotes(filterDTO, sortBy, sortOrder, page, size); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.>builder() .message("Note fetched successfully") .data(pageResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @PatchMapping("/{id}") @Operation(summary = "Update note") - public ResponseEntity updateNote(@PathVariable int id, @RequestBody @Valid NoteUpsertRequest noteUpdateRequest) { + public ApiResponse updateNote(@PathVariable int id, @RequestBody @Valid NoteUpsertRequest noteUpdateRequest) { NoteResponse note = noteService.updateNote(id, noteUpdateRequest); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Note updated successfully") .data(note) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @DeleteMapping("/{id}") @Operation(summary = "Delete note") - public ResponseEntity deleteNote(@PathVariable int id) { + public ApiResponse deleteNote(@PathVariable int id) { noteService.deleteNote(id); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Note deleted successfully") .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } } diff --git a/src/main/java/com/be08/smart_notes/controller/QuizController.java b/src/main/java/com/be08/smart_notes/controller/QuizController.java index 5f5b329..4803095 100644 --- a/src/main/java/com/be08/smart_notes/controller/QuizController.java +++ b/src/main/java/com/be08/smart_notes/controller/QuizController.java @@ -12,12 +12,11 @@ import com.be08.smart_notes.validation.group.OnUpdate; import com.fasterxml.jackson.annotation.JsonView; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -26,25 +25,25 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Tag(name = "Quizzes", description = "All operations for quizzes") +@SecurityRequirement(name = "Bearer Authentication") public class QuizController { QuizService quizService; @PostMapping @JsonView(QuizView.Detail.class) @Operation(summary = "Create new quiz with associated questions") - public ResponseEntity createQuiz(@RequestBody @Validated(OnCreate.class) QuizUpsertDTO request) { + public ApiResponse createQuiz(@RequestBody @Validated(OnCreate.class) QuizUpsertDTO request) { QuizResponse quizResponse = quizService.createQuiz(request); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz created successfully in default set") .data(quizResponse) .build(); - return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } @GetMapping @JsonView(QuizView.Basic.class) @Operation(summary = "Get all quizzes by page") - public ResponseEntity getAllQuizzes( + public ApiResponse> getAllQuizzes( @ModelAttribute QuizFilterDTO filterDTO, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_ORDER) String sortOrder, @@ -52,44 +51,40 @@ public ResponseEntity getAllQuizzes( @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size ) { PageResponse quizResponseList = quizService.getAllQuizzes(filterDTO, sortBy, sortOrder, page, size); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.>builder() .message("Quizzes fetched successfully") .data(quizResponseList) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @GetMapping("/{id}") @JsonView(QuizView.Detail.class) @Operation(summary = "Get quiz and all of its questions") - public ResponseEntity getQuiz(@PathVariable int id) { + public ApiResponse getQuiz(@PathVariable int id) { QuizResponse quizResponse = quizService.getQuizById(id); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz fetched successfully") .data(quizResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @PatchMapping("/{id}") @JsonView(QuizView.Basic.class) @Operation(summary = "Update quiz information") - public ResponseEntity updateQuiz(@PathVariable int id, @Validated(OnUpdate.class) @RequestBody QuizUpsertDTO request) { + public ApiResponse updateQuiz(@PathVariable int id, @Validated(OnUpdate.class) @RequestBody QuizUpsertDTO request) { QuizResponse quizResponse = quizService.updateQuiz(id, request); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz updated successfully") .data(quizResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @DeleteMapping("/{id}") @Operation(summary = "Delete quiz") - public ResponseEntity deleteQuiz(@PathVariable int id) { + public ApiResponse deleteQuiz(@PathVariable int id) { quizService.deleteQuizById(id); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz set deleted successfully") .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } } diff --git a/src/main/java/com/be08/smart_notes/controller/QuizSetController.java b/src/main/java/com/be08/smart_notes/controller/QuizSetController.java index ce911e2..74deca0 100644 --- a/src/main/java/com/be08/smart_notes/controller/QuizSetController.java +++ b/src/main/java/com/be08/smart_notes/controller/QuizSetController.java @@ -11,13 +11,12 @@ import com.be08.smart_notes.service.QuizSetService; import com.fasterxml.jackson.annotation.JsonView; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @@ -25,103 +24,96 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Tag(name = "Quiz Sets", description = "All operations for quiz sets") +@SecurityRequirement(name = "Bearer Authentication") public class QuizSetController { QuizSetService quizSetService; @PostMapping @JsonView(QuizView.Detail.class) @Operation(summary = "Create new quiz set") - public ResponseEntity createQuizSet(@RequestBody @Valid QuizSetUpsertRequest request) { + public ApiResponse createQuizSet(@RequestBody @Valid QuizSetUpsertRequest request) { QuizSetResponse quizSetResponse = quizSetService.createQuizSet(request, OriginType.USER); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz set created successfully") .data(quizSetResponse) .build(); - return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } @GetMapping @JsonView(QuizView.Basic.class) @Operation(summary = "Get all quiz sets") - public ResponseEntity getAllQuizSets( + public ApiResponse> getAllQuizSets( @ModelAttribute BasicFilterDTO filterDTO, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, @RequestParam(required = false, defaultValue = DefaultConstants.SORT_ORDER) String sortOrder, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { PageResponse quizSetResponseList = quizSetService.getAllQuizSets(filterDTO, sortBy, sortOrder, page, size); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.>builder() .message("Quiz set fetched successfully") .data(quizSetResponseList) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @GetMapping("/default") @JsonView(QuizView.Detail.class) @Operation(summary = "Get default quiz set and its quizzes") - public ResponseEntity getDefaultQuizSet() { + public ApiResponse getDefaultQuizSet() { QuizSetResponse quizSetResponse = quizSetService.getDefaultSet(); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz set fetched successfully") .data(quizSetResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @GetMapping("/{id}") @JsonView(QuizView.Basic.class) @Operation(summary = "Get quiz set") - public ResponseEntity getQuizSet(@PathVariable int id) { + public ApiResponse getQuizSet(@PathVariable int id) { QuizSetResponse quizSetResponse = quizSetService.getQuizSetById(id, false); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz set fetched successfully") .data(quizSetResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @GetMapping("/{id}/quizzes") @JsonView(QuizView.Detail.class) @Operation(summary = "Get quiz set and its quizzes") - public ResponseEntity getQuizSetWithQuizzes(@PathVariable int id) { + public ApiResponse getQuizSetWithQuizzes(@PathVariable int id) { QuizSetResponse quizSetResponse = quizSetService.getQuizSetById(id, true); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz set fetched successfully") .data(quizSetResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @PatchMapping("/{id}") @JsonView(QuizView.Detail.class) @Operation(summary = "Update quiz set information") - public ResponseEntity updateQuizSet(@PathVariable int id, @RequestBody QuizSetUpsertRequest request) { + public ApiResponse updateQuizSet(@PathVariable int id, @RequestBody QuizSetUpsertRequest request) { QuizSetResponse quizSetResponse = quizSetService.updateQuizSet(id, request); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz set updated successfully") .data(quizSetResponse) .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @DeleteMapping @Operation(summary = "Delete all quiz sets") - public ResponseEntity deleteAllQuizSet() { + public ApiResponse deleteAllQuizSet() { quizSetService.deleteAllQuizSet(); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("All quiz set deleted successfully") .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } @DeleteMapping("/{id}") @Operation(summary = "Delete quiz set") - public ResponseEntity deleteQuizSet(@PathVariable int id) { + public ApiResponse deleteQuizSet(@PathVariable int id) { quizSetService.deleteQuizSetById(id); - ApiResponse apiResponse = ApiResponse.builder() + return ApiResponse.builder() .message("Quiz set deleted successfully") .build(); - return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } } diff --git a/src/main/java/com/be08/smart_notes/controller/UserController.java b/src/main/java/com/be08/smart_notes/controller/UserController.java index 4121977..de83a51 100644 --- a/src/main/java/com/be08/smart_notes/controller/UserController.java +++ b/src/main/java/com/be08/smart_notes/controller/UserController.java @@ -4,6 +4,7 @@ import com.be08.smart_notes.dto.response.UserResponse; import com.be08.smart_notes.service.UserService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -17,6 +18,7 @@ @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @Tag(name = "User", description = "All operations for user-related features") +@SecurityRequirement(name = "Bearer Authentication") public class UserController { UserService userService; diff --git a/src/main/java/com/be08/smart_notes/dto/filter/QuizFilterDTO.java b/src/main/java/com/be08/smart_notes/dto/filter/QuizFilterDTO.java index f98dc72..301269e 100644 --- a/src/main/java/com/be08/smart_notes/dto/filter/QuizFilterDTO.java +++ b/src/main/java/com/be08/smart_notes/dto/filter/QuizFilterDTO.java @@ -10,6 +10,6 @@ @NoArgsConstructor @AllArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) -public class QuizFilterDTO extends BasicFilterDTO{ +public class QuizFilterDTO extends BasicFilterDTO { Integer quizSetId; } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index ce3a5b9..a6b6299 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,4 +1,5 @@ springdoc.api-docs.path=/api-docs +springdoc.swagger-ui.url=/api-docs springdoc.swagger-ui.tagsSorter=alpha # Database Configuration for Development (Local MySQL) From 24dfd72a617f0f31661083bfd1fe430c1800fbbb Mon Sep 17 00:00:00 2001 From: Tat Uyen Tam Date: Fri, 20 Mar 2026 20:58:59 +0700 Subject: [PATCH 3/4] docs: update README --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c270833..bf9b5af 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Backend service for **SmartNotes**, a personal project that enables smart note-taking with AI integration to help individuals have a better experience in note-taking, organizing, and revising knowledge. -**SmartNotes Frontend Repository:** [https://github.com/pvdev1805/SmartNotes](https://github.com/pvdev1805/SmartNotes) +**SmartNotes Frontend Repository:** [https://github.com/TUT888/SmartNotes-FE](https://github.com/TUT888/SmartNotes-FE) ## Table of Contents @@ -20,6 +20,7 @@ help individuals have a better experience in note-taking, organizing, and revisi - [Prerequisites](#prerequisites) - [HuggingFace API](#huggingface-api) - [Environment Variables](#environment-variables) +- [How to Run](#how-to-run) - [How to Test](#how-to-test) - [Test Commands](#test-commands-windows) - [Test Structure](#test-structure) @@ -123,6 +124,16 @@ REDIS_PORT= [Back to top](#smartnotes-backend) +## How to run +1. Start Redis server with Docker Desktop: + ```bash + docker run --name smart-notes-redis -p 6379:6379 redis:latest + ``` +2. Run the application (Windows) + ```bash + ./mvnw.cmd spring-boot:run + ``` + ## How to test ### Test commands (Windows) Run all tests @@ -177,11 +188,11 @@ src/test/java/ ## Contributors **Project Maintainers:** This project (both frontend and backend) is developed and maintained by: -- **Alice Tat** ([@TUT888](https://github.com/TUT888)) -- **Phu Vo** ([@pvdev1805](https://github.com/pvdev1805)) +- Owner: **Alice Tat** ([@TUT888](https://github.com/TUT888)) +- Contributor: **Phu Vo** ([@pvdev1805](https://github.com/pvdev1805)) **Project Repositories:** - Backend: [SmartNotes Backend](https://github.com/TUT888/SmartNotes) -- Frontend: [SmartNotes Frontend](https://github.com/pvdev1805/SmartNotes) +- Frontend: [SmartNotes Frontend](https://github.com/TUT888/SmartNotes-FE) [Back to top](#smartnotes-backend) \ No newline at end of file From dcc0ebbf86e2b899203ccff5492e90ca552023d8 Mon Sep 17 00:00:00 2001 From: Tat Uyen Tam Date: Fri, 20 Mar 2026 21:36:00 +0700 Subject: [PATCH 4/4] fix: update response status code for relevant endpoints --- .../controller/AIGenerationController.java | 13 ++++++++----- .../smart_notes/controller/AttemptController.java | 2 ++ .../controller/AuthenticationController.java | 7 +++---- .../smart_notes/controller/FlashcardController.java | 2 ++ .../controller/FlashcardSetController.java | 2 ++ .../be08/smart_notes/controller/NoteController.java | 2 ++ .../be08/smart_notes/controller/QuizController.java | 2 ++ .../smart_notes/controller/QuizSetController.java | 2 ++ .../service/ai/QuizGenerationService.java | 2 +- 9 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java b/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java index a3733c6..eebe31b 100644 --- a/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java +++ b/src/main/java/com/be08/smart_notes/controller/AIGenerationController.java @@ -12,6 +12,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -27,16 +28,17 @@ public class AIGenerationController { QuizGenerationService quizGenerationService; @GetMapping("/quiz-sets/sample") - @Operation(summary = "Generate sample quiz set") - public ApiResponse generateSampleQuiz() { - QuizResponse quizResponse = quizGenerationService.generateSampleQuiz(); + @Operation(summary = "Get sample generated quiz") + public ApiResponse getSampleQuiz() { + QuizResponse quizResponse = quizGenerationService.getSampleQuiz(); return ApiResponse.builder() - .message("Sample quiz set successfully generated") + .message("Sample quiz successfully generated") .data(quizResponse) .build(); } @PostMapping("/quiz-sets/default") + @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "Generate single quiz based given request, stored in default quiz set") public ApiResponse generateQuiz(@Validated(SingleDocument.class) @RequestBody QuizGenerationRequest quizGenerationRequest) { QuizResponse quizSetResponse = quizGenerationService.generateQuiz(quizGenerationRequest); @@ -47,11 +49,12 @@ public ApiResponse generateQuiz(@Validated(SingleDocument.class) @ } @PostMapping("/quiz-sets") + @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "Generate multiple quizzes based given request, grouped in new quiz set") public ApiResponse generateQuizSet(@Validated(MultipleDocument.class) @RequestBody QuizGenerationRequest quizGenerationRequest) { QuizSetResponse quizSetResponse = quizGenerationService.generateQuizSet(quizGenerationRequest); return ApiResponse.builder() - .message("Quiz set successfully generated") + .message("Quizzes successfully generated and added to new quiz set") .data(quizSetResponse) .build(); } diff --git a/src/main/java/com/be08/smart_notes/controller/AttemptController.java b/src/main/java/com/be08/smart_notes/controller/AttemptController.java index 0154756..71a0aa2 100644 --- a/src/main/java/com/be08/smart_notes/controller/AttemptController.java +++ b/src/main/java/com/be08/smart_notes/controller/AttemptController.java @@ -15,6 +15,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -28,6 +29,7 @@ public class AttemptController { AttemptService attemptService; @PostMapping("/{quizId}/attempts") + @ResponseStatus(HttpStatus.CREATED) @JsonView(AttemptView.Detail.class) @Operation(summary = "Create new attempt for target quiz") public ApiResponse createAttempt(@PathVariable int quizId) { diff --git a/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java b/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java index be9d1b3..a6d5c46 100644 --- a/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java +++ b/src/main/java/com/be08/smart_notes/controller/AuthenticationController.java @@ -15,11 +15,9 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") @@ -36,6 +34,7 @@ public class AuthenticationController { * @return ApiResponse containing UserResponse */ @PostMapping("/register") + @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "Register new user") ApiResponse register(@RequestBody @Valid UserCreationRequest request){ UserResponse response = userService.createUser(request); diff --git a/src/main/java/com/be08/smart_notes/controller/FlashcardController.java b/src/main/java/com/be08/smart_notes/controller/FlashcardController.java index 22fbe7b..9b8dff5 100644 --- a/src/main/java/com/be08/smart_notes/controller/FlashcardController.java +++ b/src/main/java/com/be08/smart_notes/controller/FlashcardController.java @@ -11,6 +11,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController @@ -34,6 +35,7 @@ public ApiResponse createFlashcard(@RequestBody FlashcardCrea } @GetMapping("/{flashcardId}") + @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "Create flashcard") public ApiResponse getFlashcard(@PathVariable int flashcardId){ FlashcardResponse response = flashcardService.getFlashcardById(flashcardId); diff --git a/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java b/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java index 1617e7a..3850d0a 100644 --- a/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java +++ b/src/main/java/com/be08/smart_notes/controller/FlashcardSetController.java @@ -13,6 +13,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -33,6 +34,7 @@ public class FlashcardSetController { * @return ApiResponse containing FlashcardSetResponse */ @PostMapping + @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "Create flashcard set") public ApiResponse createFlashcardSet(@RequestBody @Valid FlashcardSetCreationRequest request){ FlashcardSetResponse response = flashcardSetService.createFlashcardSet(request); diff --git a/src/main/java/com/be08/smart_notes/controller/NoteController.java b/src/main/java/com/be08/smart_notes/controller/NoteController.java index 2d0be24..5fadd4a 100644 --- a/src/main/java/com/be08/smart_notes/controller/NoteController.java +++ b/src/main/java/com/be08/smart_notes/controller/NoteController.java @@ -11,6 +11,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import com.be08.smart_notes.dto.request.NoteUpsertRequest; @@ -27,6 +28,7 @@ public class NoteController { NoteService noteService; @PostMapping + @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "Create new note") public ApiResponse createNote(@RequestBody @Valid NoteUpsertRequest noteCreationRequest) { NoteResponse createdNote = noteService.createNote(noteCreationRequest); diff --git a/src/main/java/com/be08/smart_notes/controller/QuizController.java b/src/main/java/com/be08/smart_notes/controller/QuizController.java index 4803095..bdfd8ef 100644 --- a/src/main/java/com/be08/smart_notes/controller/QuizController.java +++ b/src/main/java/com/be08/smart_notes/controller/QuizController.java @@ -17,6 +17,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -30,6 +31,7 @@ public class QuizController { QuizService quizService; @PostMapping + @ResponseStatus(HttpStatus.CREATED) @JsonView(QuizView.Detail.class) @Operation(summary = "Create new quiz with associated questions") public ApiResponse createQuiz(@RequestBody @Validated(OnCreate.class) QuizUpsertDTO request) { diff --git a/src/main/java/com/be08/smart_notes/controller/QuizSetController.java b/src/main/java/com/be08/smart_notes/controller/QuizSetController.java index 74deca0..f62433e 100644 --- a/src/main/java/com/be08/smart_notes/controller/QuizSetController.java +++ b/src/main/java/com/be08/smart_notes/controller/QuizSetController.java @@ -17,6 +17,7 @@ import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController @@ -29,6 +30,7 @@ public class QuizSetController { QuizSetService quizSetService; @PostMapping + @ResponseStatus(HttpStatus.CREATED) @JsonView(QuizView.Detail.class) @Operation(summary = "Create new quiz set") public ApiResponse createQuizSet(@RequestBody @Valid QuizSetUpsertRequest request) { diff --git a/src/main/java/com/be08/smart_notes/service/ai/QuizGenerationService.java b/src/main/java/com/be08/smart_notes/service/ai/QuizGenerationService.java index 2f12001..7be655c 100644 --- a/src/main/java/com/be08/smart_notes/service/ai/QuizGenerationService.java +++ b/src/main/java/com/be08/smart_notes/service/ai/QuizGenerationService.java @@ -75,7 +75,7 @@ public QuizGenerationService(AIService aiService, NoteService noteService, QuizS * A sample function to retrieve a quiz for testing purposes, it will neither interact with the AI provider nor save data to the database. * @return quiz response dto */ - public QuizResponse generateSampleQuiz() { + public QuizResponse getSampleQuiz() { // Below is sample generated content String generatedContent = "{\"topic\":\"Object-Oriented Programming Concepts\",\"questions\":[{\"question\":\"What is the main purpose of Object-Oriented Programming (OOP)?\",\"options\":[\"A. To simplify data structures\",\"B. To organize code around objects\",\"C. To create complex algorithms\",\"D. To optimize code execution speed\"], \"correct_index\": 1}, {\"question\":\"Which of the following best describes encapsulation in OOP?\",\"options\":[\"A. Hiding internal data and exposing only necessary information\",\"B. Creating multiple objects from a single class\",\"C. Passing data between different classes\",\"D. Defining the structure of a class\",\"\"], \"correct_index\": 1}, {\"question\":\"What is the primary function of a constructor in OOP?\",\"options\":[\"A. To delete an object from memory\",\"B. To store data for an object\",\"C. To initialize an object when it is created\",\"D. To define the behavior of an object\",\"\"], \"correct_index\": 3}, {\"question\":\"How does inheritance work in OOP?\",\"options\":[\"A. It allows objects to inherit properties and methods from other objects\",\"B. It creates a new class based on an existing one and adds new features\",\"C. It allows objects to access private members of other objects\",\"D. It enables objects to communicate with each other through messages\",\"\"], \"correct_index\": 1}, {\"question\":\"What does polymorphism refer to in OOP?\",\"options\":[\"A. The ability of an object to be accessed from multiple classes\",\"B. The ability of an object to behave differently based on its context\",\"C. The ability of an object to be used in different programming languages\",\"D. The ability of an object to be inherited from other objects\",\"\"], \"correct_index\": 1}, {\"question\":\"Which of the following is NOT a benefit of OOP?\",\"options\":[\"A. Improved code reusability\",\"B. Easier code maintenance\",\"C. Increased program complexity\",\"D. Enhanced code readability\",\"\"], \"correct_index\": 3}, {\"question\":\"What is the primary difference between a class and an object?\",\"options\":[\"A. A class is a blueprint for creating objects, while an object is an instance of that blueprint\",\"B. A class is a data structure, while an object is a programming language\",\"C. A class is a variable, while an object is a function\",\"D. A class is a method, while an object is a program\",\"\"], \"correct_index\": 1}, {\"question\":\"What is the main purpose of a static method?\",\"options\":[\"A. To define a method that is specific to a particular object\",\"B. To define a method that belongs to a class and not to individual objects\",\"C. To define a method that is called when an object is created\",\"D. To define a method that is called when an object is destroyed\",\"\"], \"correct_index\": 1}, {\"question\":\"Which of the following is an example of a common mistake to avoid in OOP?\",\"options\":[\"A. Using inheritance when it is not needed\",\"B. Using public access modifiers for every method\",\"C. Using static methods for every method\",\"D. Creating complex objects that are not needed\",\"\"], \"correct_index\": 1}, {\"question\":\"What is the purpose of an interface in OOP?\",\"options\":[\"A. To define the behavior of a class\",\"B. To create a contract that classes must follow\",\"C. To define the structure of a class\",\"D. To create a blueprint for creating objects\",\"\"], \"correct_index\": 1}, {\"question\":\"What is the purpose of a method overriding?\",\"options\":[\"A. To create a new class that is based on an existing one\",\"B. To define a new method with a different implementation in a child class\",\"C. To create a new method that overrides the behavior of a parent class\",\"D. To create a new class that inherits from a different class\",\"\"], \"correct_index\": 3}]}\n";