Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>gg.jte</groupId>-->
<!-- <artifactId>jte-spring-boot-starter-4</artifactId>-->
<!-- <version>3.2.3</version>-->
<!-- </dependency>-->

<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-docker-compose</artifactId>-->
Expand Down
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";
}

}
35 changes: 27 additions & 8 deletions src/main/java/org/example/lab1springboot/book/BookService.java
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
Expand Up @@ -5,17 +5,24 @@
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.Getter;

import lombok.Setter;

@Getter
@Setter
@Data
public class CreateBookDTO {

@NotBlank(message = "name cannot be empty")
private String name;

@NotBlank(message = "title cannot be empty")
private String title;
@NotBlank(message = "author cannot be empty")
private String author;


@NotBlank(message = "Description cannot be empty")
private String description;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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";
Expand All @@ -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) {
Comment on lines 57 to 58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Prefix the edit and delete endpoints consistently.

src/main/resources/templates/books.html links 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
-    `@PutMapping`("/{id}")
+    `@PutMapping`("/books/{id}")
     public String updateBook(`@PathVariable` Long id, `@Valid` UpdateBookDTO updateBookDTO, BindingResult bindingResult, Model model) {
@@
-    `@DeleteMapping`("/{id}")
+    `@DeleteMapping`("/books/{id}")
     public String deleteBook(`@PathVariable` Long id) {
@@
-    `@GetMapping`("/update/{id}")
+    `@GetMapping`("/books/update/{id}")
     public String showUpdateForm(`@PathVariable` Long id, Model model) {

Also applies to: 68-75

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/lab1springboot/controller/BookController.java`
around lines 57 - 58, The route annotations in BookController (e.g., updateBook)
don't match the URLs used by the templates (/books/update/{id} and posts to
/books/{id}), causing 404s; update the mapping annotations so they are prefixed
with /books (for example change `@PutMapping`("/{id}") to
`@PutMapping`("/books/{id}") and any `@GetMapping` for the edit form to
`@GetMapping`("/books/update/{id}")), and likewise adjust the delete handler
mapping (e.g., deleteBook) to `@PostMapping` or `@DeleteMapping`("/books/{id}") as
the template expects; update the annotations on the methods named updateBook,
deleteBook (and the edit form handler, if present) so the controller paths and
templates are consistent.

if (bindingResult.hasErrors()) {
model.addAttribute("updateBookDTO", updateBookDTO);
return "updatebook";
return "update-book";
}

bookService.updateBook(id, updateBookDTO);
Expand All @@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

showUpdateForm populates the wrong model attributes for the template.

update-book.html binds to ${updateBookDTO} and uses ${id}, but this method only exposes "book". The edit page will fail to bind correctly on the initial GET render.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/lab1springboot/controller/BookController.java`
around lines 75 - 79, The showUpdateForm method in BookController currently adds
only "book" to the model but the template expects "updateBookDTO" and "id";
change showUpdateForm (which calls bookService.getBookById) to map the retrieved
Book into an UpdateBookDTO instance, add model.addAttribute("updateBookDTO",
updateDto) and model.addAttribute("id", id) (or model.addAttribute("id",
book.getId())), so the template can bind to ${updateBookDTO} and ${id}; use the
UpdateBookDTO constructor or setters to populate fields from Book before
returning "update-book".

}

@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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject invalid paging parameters up front.

PageRequest.of(page, size) will throw for page < 0 or size < 1, so a bad query string currently turns into a 500 instead of a client error.

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
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/lab1springboot/controller/BookController.java`
around lines 101 - 107, Validate the paging parameters in the controller before
calling bookService.getAllBooksPaginated: in getAllBooks check that page >= 0
and size >= 1 and if not throw a client error (e.g., throw new
ResponseStatusException(HttpStatus.BAD_REQUEST, "page must be >= 0 and size must
be >= 1")) so a bad query string yields HTTP 400 instead of allowing
PageRequest.of(page, size) (or any similar call inside
bookService.getAllBooksPaginated) to throw a 500.


model.addAttribute("books", bookPage.getContent());
model.addAttribute("currentPage", page);
model.addAttribute("totalPages", bookPage.getTotalPages());

return "books";
}



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ public class HomeController {
@GetMapping("/")
public String home(Model model) {

System.out.println("DEBUG: Home controller triggered");

model.addAttribute("message", "Welcome to Thymeleaf!");
model.addAttribute("currentDate", java.time.LocalDate.now());

model.addAttribute("activePage", "home");
return "home";
}

}
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Return an existing view or add user.html.

The success path returns "user", but the provided templates only include books.html, create-book.html, home.html, navbar.html, and update-book.html. As written, a successful POST /user still fails during view resolution.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/lab1springboot/controller/UserController.java`
around lines 12 - 15, The CreateUser(`@RequestBody` `@Valid` User user) handler in
UserController returns the view name "user" which doesn't exist in the templates
and will fail view resolution; fix by either creating a new user.html template
matching that view name or changing the handler to return an existing view
(e.g., "home") or a redirect (e.g., "redirect:/home") after successful
creation—update the method in UserController (CreateUser) accordingly so the
returned view name matches an actual template or uses redirect to an existing
route.



}
}
31 changes: 31 additions & 0 deletions src/main/java/org/example/lab1springboot/user/User.java
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

These constraints still allow blank user data.

@NotNull accepts "", and @NotEmpty on username still allows whitespace-only values. If this object is used for request validation, blank name, username, and email inputs will pass despite the current messages. Use @NotBlank, and add @Email for email.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@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;
`@NotBlank`(message = "Name cannot be empty")
private String name;
`@NotBlank`(message = "Username cannot be empty")
private String username;
`@NotBlank`(message = "Email cannot be empty")
`@Email`(message = "Email is invalid")
private String email;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/lab1springboot/user/User.java` around lines 13 -
21, The validation annotations on User fields allow blank/whitespace values;
replace `@NotNull/`@NotEmpty with `@NotBlank` on the name, username, and email
fields (i.e., the private String name, private String username, private String
email in class User) to reject empty or whitespace-only input, and add `@Email` to
the email field to enforce proper email format; keep the message attributes (or
adjust them) when updating the annotations so validation messages remain
informative.


@NotBlank(message = "Password cannot be blank")
private String password;

@NotNull(message = "Id cannot be empty")
private String id;
Comment on lines +26 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t require a client-supplied id on the create model.

This type is validated on POST /user, so @NotNull here forces callers to invent an identifier before creation. That usually breaks create flows or lets clients pick IDs that should be server-controlled.

Suggested direction
-    `@NotNull`(message = "Id cannot be empty")
     private String id;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@NotNull(message = "Id cannot be empty")
private String id;
private String id;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/lab1springboot/user/User.java` around lines 26 -
27, The User model currently requires a client-supplied id via the `@NotNull`
annotation on the id field which prevents normal server-side ID generation for
POST /user; remove the `@NotNull` on the id field in class User (or better:
introduce a CreateUser DTO without an id and validate that instead) and ensure
ID is assigned server-side in the controller/service that handles POST /user
(refer to User.id, the `@NotNull` annotation, and the POST /user flow when making
the change).




}
Loading