-
Notifications
You must be signed in to change notification settings - Fork 0
Refactored templates, controllers, and styles for improved book manag… #7
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.example.lab1springboot; | ||
|
|
||
| public class BookNotFoundException extends RuntimeException { | ||
| public BookNotFoundException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package org.example.lab1springboot; | ||
|
|
||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.ui.Model; | ||
| import org.springframework.validation.FieldError; | ||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||
| import org.springframework.web.bind.annotation.ControllerAdvice; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| import org.springframework.web.bind.annotation.ResponseStatus; | ||
| import org.springframework.web.servlet.View; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| import static org.springframework.util.ReflectionUtils.getField; | ||
|
|
||
| /* | ||
| Global exception handler to handle all exceptions more effectively | ||
|
|
||
| */ | ||
| @ControllerAdvice | ||
| public class GlobalExceptionHandler { | ||
|
|
||
|
|
||
|
|
||
| @ExceptionHandler(BookNotFoundException.class) | ||
| @ResponseStatus(HttpStatus.NOT_FOUND) // 404 | ||
| public String notFoundException(BookNotFoundException ex, Model model) { | ||
|
|
||
| model.addAttribute("errorMessage", ex.getMessage()); | ||
|
|
||
| return "error"; | ||
| } | ||
|
|
||
| @ExceptionHandler(RuntimeException.class) | ||
| @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 500 | ||
| public String handleRuntimeException(Exception ex, Model model) { | ||
|
|
||
| model.addAttribute("errorMessage", "Unexpected error"); | ||
| return "error"; | ||
| } | ||
|
|
||
| @ExceptionHandler(MethodArgumentNotValidException.class) | ||
| @ResponseStatus(HttpStatus.BAD_REQUEST) | ||
| public String handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, Model model) { | ||
| Map<String, String> errors = new HashMap<>(); | ||
|
|
||
| ex.getBindingResult().getFieldErrors().forEach(error -> | ||
| errors.put(error.getField(), error.getDefaultMessage()) | ||
| ); | ||
|
|
||
| model.addAttribute("errors", errors); | ||
| return "error"; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,55 +1,74 @@ | ||
| package org.example.lab1springboot.book; | ||
|
|
||
|
|
||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.PageRequest; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.example.lab1springboot.BookNotFoundException; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.util.List; | ||
|
|
||
|
|
||
| @Slf4j | ||
| @Service | ||
|
|
||
| public class BookService { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(BookService.class); | ||
| private final BookRepository bookRepository; | ||
|
|
||
| public BookService(BookRepository bookRepository) { | ||
| log.info("BookService created"); | ||
| this.bookRepository = bookRepository; | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public List<Book> getAllBooks() { | ||
|
|
||
| return bookRepository.findAll(); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void deleteBook(Long id) { | ||
| if (!bookRepository.existsById(id)) { | ||
| throw new RuntimeException("Book not found with id: " + id); | ||
| } | ||
| if(bookRepository.findById(id).isEmpty()) | ||
| throw new BookNotFoundException("Book not found with id: " + id); | ||
|
|
||
| bookRepository.deleteById(id); | ||
| } | ||
|
|
||
| @Transactional | ||
| public Book getBookById(Long id) { | ||
| return bookRepository.findById(id) | ||
| .orElseThrow(() -> new RuntimeException("Book not found with id: " + id)); | ||
| .orElseThrow(() -> new BookNotFoundException("Book not found with id: " + id)); | ||
| } | ||
|
|
||
| @Transactional | ||
| public Book updateBook(Long id, UpdateBookDTO dto) { | ||
| Book book = getBookById(id); | ||
| book.setTitle(dto.getTitle()); | ||
| book.setAuthor(dto.getName()); | ||
|
|
||
| bookRepository.save(book); | ||
| Book updatedBook = bookRepository.save(book); | ||
|
|
||
| log.info("Book updated successfully"); | ||
| return book; | ||
| return updatedBook; | ||
| } | ||
|
|
||
| @Transactional | ||
| public Book createBook(CreateBookDTO dto) { | ||
|
|
||
| Book book = BookMapper.toEntity(dto); | ||
|
|
||
| return bookRepository.save(book); | ||
| } | ||
|
|
||
| public Page<Book> getAllBooksPaginated(int page, int size){ | ||
| Pageable pageable = PageRequest.of(page, size); | ||
| return bookRepository.findAll(pageable); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,16 +2,29 @@ | |
|
|
||
|
|
||
| import jakarta.validation.Valid; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.example.lab1springboot.BookNotFoundException; | ||
| import org.example.lab1springboot.book.*; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.ui.Model; | ||
| import org.springframework.validation.BindingResult; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
| @Controller | ||
| @RequestMapping("/books") | ||
|
|
||
| public class BookController { | ||
|
|
||
| Logger log = LoggerFactory.getLogger(BookController.class); | ||
|
|
||
|
|
||
| BookService bookService; | ||
|
|
||
|
|
@@ -22,26 +35,20 @@ public class BookController { | |
|
|
||
|
|
||
|
|
||
| @GetMapping("/{id}") | ||
| public String getBookById(@PathVariable Long id, Model model) { | ||
| Book book = bookService.getBookById(id); | ||
| model.addAttribute("book", book); | ||
| return "books"; | ||
| } | ||
|
|
||
| @GetMapping | ||
| public String getAllBooks(Model model) { | ||
| model.addAttribute("books", bookService.getAllBooks()); | ||
| return "books"; | ||
| } | ||
|
|
||
| // @GetMapping("/{id}") | ||
| // public String getBookById(@PathVariable Long id, Model model) { | ||
| // Book book = bookService.getBookById(id); | ||
| // | ||
| // model.addAttribute("book", book); | ||
| // return "books"; | ||
| // } | ||
|
|
||
|
|
||
| @PostMapping("books/create") | ||
| public String createBook(@Valid CreateBookDTO dto, BindingResult result) { | ||
|
|
||
| if(result.hasErrors()){ | ||
| return "createbook"; | ||
| return "create-book"; | ||
| } | ||
| bookService.createBook(dto); | ||
| return "redirect:/books"; | ||
|
|
@@ -50,8 +57,7 @@ public String createBook(@Valid CreateBookDTO dto, BindingResult result) { | |
| @PutMapping("/{id}") | ||
| public String updateBook(@PathVariable Long id, @Valid UpdateBookDTO updateBookDTO, BindingResult bindingResult, Model model) { | ||
| if (bindingResult.hasErrors()) { | ||
| model.addAttribute("updateBookDTO", updateBookDTO); | ||
| return "updatebook"; | ||
| return "update-book"; | ||
| } | ||
|
|
||
| bookService.updateBook(id, updateBookDTO); | ||
|
|
@@ -61,10 +67,51 @@ public String updateBook(@PathVariable Long id, @Valid UpdateBookDTO updateBookD | |
|
|
||
| @DeleteMapping("/{id}") | ||
| public String deleteBook(@PathVariable Long id) { | ||
|
|
||
| bookService.deleteBook(id); | ||
| return "redirect:/books"; | ||
| } | ||
|
|
||
| @GetMapping("/update/{id}") | ||
| public String showUpdateForm(@PathVariable Long id, Model model) { | ||
| Book book = bookService.getBookById(id); | ||
|
|
||
| UpdateBookDTO dto = new UpdateBookDTO(); | ||
|
|
||
|
|
||
| model.addAttribute("updateBookDTO", dto); | ||
| model.addAttribute("id", id); | ||
| return "update-book"; | ||
|
Comment on lines
+75
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| @GetMapping("books/create") | ||
| public String showCreateForm(Model model) { | ||
|
|
||
| model.addAttribute("book", new CreateBookDTO()); | ||
| return "create-book"; | ||
| } | ||
|
|
||
| @ExceptionHandler(BookNotFoundException.class) | ||
| @ResponseStatus(HttpStatus.NOT_FOUND) | ||
| public String handleBookNotFound(BookNotFoundException ex, Model model) { | ||
| model.addAttribute("errorMessage", ex.getMessage()); | ||
| return "error"; | ||
| } | ||
|
|
||
| @GetMapping("/books") | ||
| public String getAllBooks( | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "5") int size, | ||
| Model model) { | ||
|
|
||
| Page<Book> bookPage = bookService.getAllBooksPaginated(page, size); | ||
|
Comment on lines
+101
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject invalid paging parameters up front.
Suggested fix+import org.springframework.web.server.ResponseStatusException;
@@
public String getAllBooks(
`@RequestParam`(defaultValue = "0") int page,
`@RequestParam`(defaultValue = "5") int size,
Model model) {
+ if (page < 0 || size < 1) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid pagination parameters");
+ }
Page<Book> bookPage = bookService.getAllBooksPaginated(page, size);🤖 Prompt for AI Agents |
||
|
|
||
| model.addAttribute("books", bookPage.getContent()); | ||
| model.addAttribute("currentPage", page); | ||
| model.addAttribute("totalPages", bookPage.getTotalPages()); | ||
|
|
||
| return "books"; | ||
| } | ||
|
|
||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package org.example.lab1springboot.controller; | ||
|
|
||
| import jakarta.validation.Valid; | ||
| import org.example.lab1springboot.user.User; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
|
|
||
| @Controller | ||
| public class UserController { | ||
|
|
||
| @PostMapping("/user") | ||
| public String CreateUser(@RequestBody @Valid User user) { | ||
|
|
||
| return "user"; | ||
|
Comment on lines
+12
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return an existing view or add The success path returns 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||||||||||||||
| package org.example.lab1springboot.user; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import jakarta.validation.constraints.NotBlank; | ||||||||||||||||||||||||||||||||||||||
| import jakarta.validation.constraints.NotEmpty; | ||||||||||||||||||||||||||||||||||||||
| import jakarta.validation.constraints.NotNull; | ||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||
| import lombok.Setter; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||
| @Setter | ||||||||||||||||||||||||||||||||||||||
| public class User { | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @NotNull(message = "Name cannot be empty") | ||||||||||||||||||||||||||||||||||||||
| private String name; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @NotEmpty | ||||||||||||||||||||||||||||||||||||||
| @NotNull(message = "Username cannot be empty") | ||||||||||||||||||||||||||||||||||||||
| private String username; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @NotNull(message = "Email cannot be empty") | ||||||||||||||||||||||||||||||||||||||
| private String email; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These constraints still allow blank user data.
Suggested fix+import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
@@
- `@NotNull`(message = "Name cannot be empty")
+ `@NotBlank`(message = "Name cannot be empty")
private String name;
@@
- `@NotEmpty`
- `@NotNull`(message = "Username cannot be empty")
+ `@NotBlank`(message = "Username cannot be empty")
private String username;
@@
- `@NotNull`(message = "Email cannot be empty")
+ `@NotBlank`(message = "Email cannot be empty")
+ `@Email`(message = "Email is invalid")
private String email;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @NotBlank(message = "Password cannot be blank") | ||||||||||||||||||||||||||||||||||||||
| private String password; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @NotNull(message = "Id cannot be empty") | ||||||||||||||||||||||||||||||||||||||
| private String id; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t require a client-supplied This type is validated on Suggested direction- `@NotNull`(message = "Id cannot be empty")
private String id;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefix the edit and delete endpoints consistently.
src/main/resources/templates/books.htmllinks to/books/update/{id}and posts to/books/{id}, but these handlers are registered at/update/{id}and/{id}. Edit and delete will 404 until the mappings match.Suggested fix
Also applies to: 68-75
🤖 Prompt for AI Agents