From a4488e8d458ad0d8614fe9f0400f38b0dac87c16 Mon Sep 17 00:00:00 2001 From: Eva Hottinen Date: Thu, 3 Apr 2025 08:49:46 +0200 Subject: [PATCH 01/37] Renamed GlobalExceptionHandler --- ...balExceptionHandler.java => GlobalRESTExceptionHandler.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/org/example/springboot25/exceptions/{GlobalExceptionHandler.java => GlobalRESTExceptionHandler.java} (96%) diff --git a/src/main/java/org/example/springboot25/exceptions/GlobalExceptionHandler.java b/src/main/java/org/example/springboot25/exceptions/GlobalRESTExceptionHandler.java similarity index 96% rename from src/main/java/org/example/springboot25/exceptions/GlobalExceptionHandler.java rename to src/main/java/org/example/springboot25/exceptions/GlobalRESTExceptionHandler.java index b492400a..28f640bd 100644 --- a/src/main/java/org/example/springboot25/exceptions/GlobalExceptionHandler.java +++ b/src/main/java/org/example/springboot25/exceptions/GlobalRESTExceptionHandler.java @@ -8,7 +8,7 @@ import java.util.Map; @RestControllerAdvice -public class GlobalExceptionHandler { +public class GlobalRESTExceptionHandler { // 404 – Generell @ExceptionHandler(NotFoundException.class) From 7d50e80d1b6e7fce86e9028e0e3d0407eb7b05ba Mon Sep 17 00:00:00 2001 From: Malva Malmgren <71336957+malvamalmgren@users.noreply.github.com> Date: Thu, 3 Apr 2025 13:09:13 +0200 Subject: [PATCH 02/37] #84 User (#11) * Update User entity and add User repository, service, controller * Update css structure and add user-details.html and user-list.html * Fix repository variable names * Update controller, service and repository * Update UserRepository and add exception handling in UserService * Update UserViewController * Update UserViewController and file structure * Update and refactor user controllers and service * Update UserViewController * Update UserRepository, UserService, UserRestController, User entity and add html * Update UserViewController * Minor fix and add temp html * Fix UserViewController if statements * Fix UserViewController add method * Cleanup and update add user form * Add check for unique username and email, and add GlobalViewExceptionHandler with UserAlreadyExistsException * Add check for unique username and email during update, and add delete account functionality * Remove sensitive info --- .../controller/UserRestController.java | 94 +++++++ .../controller/UserViewController.java | 208 ++++++++++++++++ .../example/springboot25/entities/User.java | 13 + .../GlobalViewExceptionHandler.java | 25 ++ .../UserAlreadyExistsException.java | 7 + .../repository/UserRepository.java | 41 ++++ .../springboot25/service/UserService.java | 162 ++++++++++++ src/main/resources/application.properties | 2 +- src/main/resources/static/css/main.css | 230 ++++++++++++++++++ src/main/resources/templates/error-page.html | 10 + .../resources/templates/fragments/header.html | 14 ++ src/main/resources/templates/index.html | 153 +----------- .../resources/templates/user/user-add.html | 51 ++++ .../templates/user/user-details.html | 23 ++ .../resources/templates/user/user-list.html | 26 ++ .../resources/templates/user/user-update.html | 66 +++++ 16 files changed, 974 insertions(+), 151 deletions(-) create mode 100644 src/main/java/org/example/springboot25/controller/UserRestController.java create mode 100644 src/main/java/org/example/springboot25/controller/UserViewController.java create mode 100644 src/main/java/org/example/springboot25/exceptions/GlobalViewExceptionHandler.java create mode 100644 src/main/java/org/example/springboot25/exceptions/UserAlreadyExistsException.java create mode 100644 src/main/java/org/example/springboot25/repository/UserRepository.java create mode 100644 src/main/java/org/example/springboot25/service/UserService.java create mode 100644 src/main/resources/static/css/main.css create mode 100644 src/main/resources/templates/error-page.html create mode 100644 src/main/resources/templates/fragments/header.html create mode 100644 src/main/resources/templates/user/user-add.html create mode 100644 src/main/resources/templates/user/user-details.html create mode 100644 src/main/resources/templates/user/user-list.html create mode 100644 src/main/resources/templates/user/user-update.html diff --git a/src/main/java/org/example/springboot25/controller/UserRestController.java b/src/main/java/org/example/springboot25/controller/UserRestController.java new file mode 100644 index 00000000..17df4da6 --- /dev/null +++ b/src/main/java/org/example/springboot25/controller/UserRestController.java @@ -0,0 +1,94 @@ +package org.example.springboot25.controller; + +import jakarta.validation.Valid; +import org.example.springboot25.entities.User; +import org.example.springboot25.service.UserService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/users") +public class UserRestController { + + private final UserService userService; + + public UserRestController(UserService userService) { + this.userService = userService; + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public List getAllUsers() { + return userService.getAllUsers(); + } + + @GetMapping("/id/{userId}") + @ResponseStatus(HttpStatus.OK) + public User getById(@PathVariable() Long userId) { + return userService.getUserById(userId); + } + + @GetMapping("/username/{userName}") + @ResponseStatus(HttpStatus.OK) + public User getByUserName(@PathVariable() String userName) { + return userService.getUserByUserName(userName); + } + + @GetMapping("/by-username/{userName}") + @ResponseStatus(HttpStatus.OK) + public List getAllByUserName(@PathVariable String userName){ + return userService.getAllUsersByUserName(userName); + } + + @GetMapping("/by-name/{userFullName}") + @ResponseStatus(HttpStatus.OK) + public List getAllByFullName(@PathVariable String userFullName) { + return userService.getAllUsersByFullName(userFullName); + } + + @GetMapping("/by-location/{userLocation}") + @ResponseStatus(HttpStatus.OK) + public List getAllByLocation(@PathVariable String userLocation) { + return userService.getAllUsersByLocation(userLocation); + } + + @GetMapping("/by-role/{userRole}") + @ResponseStatus(HttpStatus.OK) + public List getAllByRole(@PathVariable String userRole) { + return userService.getAllUsersByRole(userRole); + } + + @GetMapping("/by-search-term/{searchTerm}") + @ResponseStatus(HttpStatus.OK) + public List getAllByUserNameOrCatName(@PathVariable String searchTerm) { + return userService.getAllUsersByUserNameOrCatName(searchTerm); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public User addUser(@RequestBody @Valid User user) { + return userService.addUser(user); + } + + @PutMapping("/{userId}") + @ResponseStatus(HttpStatus.OK) + public User updateUser(@PathVariable Long userId, @RequestBody @Valid User user) { + return userService.updateUser(userId, user); + } + + @PatchMapping("/{userId}") + @ResponseStatus(HttpStatus.OK) + public User updateUser(@PathVariable Long userId, @RequestBody Map updates) { + return userService.updateUser(userId, updates); + } + + @DeleteMapping("/{userId}") + public ResponseEntity deleteUserById(@PathVariable Long userId) { + userService.deleteUserById(userId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/org/example/springboot25/controller/UserViewController.java b/src/main/java/org/example/springboot25/controller/UserViewController.java new file mode 100644 index 00000000..581b69dc --- /dev/null +++ b/src/main/java/org/example/springboot25/controller/UserViewController.java @@ -0,0 +1,208 @@ +package org.example.springboot25.controller; + +import jakarta.validation.Valid; +import org.example.springboot25.entities.User; +import org.example.springboot25.exceptions.NotFoundException; +import org.example.springboot25.exceptions.UserAlreadyExistsException; +import org.example.springboot25.service.UserService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.util.List; +import java.util.Map; + +@Controller +@RequestMapping("/users") +public class UserViewController { + private final UserService userService; + + public UserViewController(UserService userService) { + this.userService = userService; + } + + @GetMapping + public String getUsers(Model model) { + List allUsers = userService.getAllUsers(); + if (allUsers.isEmpty()) { + model.addAttribute("usersError", "No users found."); + } + model.addAttribute("users", allUsers); + return "user/user-list"; + } + + @GetMapping("/profile/id/{userId}") + public String getUserById(@PathVariable() Long userId, Model model) { + try { + User user = userService.getUserById(userId); + model.addAttribute("user", user); + return "user/user-details"; + } catch (NotFoundException e) { + model.addAttribute("error", e.getMessage()); + return "error-page"; + } + } + + @GetMapping("/profile/{userName}") + String getUserByUserName(@PathVariable String userName, Model model) { + try { + User user = userService.getUserByUserName(userName); + model.addAttribute("user", user); + return "user/user-details"; + } catch (NotFoundException e) { + model.addAttribute("error", e.getMessage()); + return "error-page"; + } + } + + @GetMapping("/by-email/{userEmail}") + String getUserByUserEmail(@PathVariable String userEmail, Model model) { + try { + User user = userService.getUserByEmail(userEmail); + model.addAttribute("user", user); + return "user/user-details"; + } catch (NotFoundException e) { + model.addAttribute("error", e.getMessage()); + return "error-page"; + } + } + + @GetMapping("/by-username/{userName}") + String getUsersByUserName(@PathVariable String userName, Model model) { + List users = userService.getAllUsersByUserName("%" + userName + "%"); + if (users.isEmpty()) + model.addAttribute("message", "No users found with username '" + userName + "'."); + model.addAttribute("users", users); + return "user/user-list"; + } + + @GetMapping("/by-name/{userFullName}") + String getUsersByFullName(@PathVariable String userFullName, Model model) { + List users = userService.getAllUsersByFullName("%" + userFullName + "%"); + if (users.isEmpty()) + model.addAttribute("message", "No users found for name '" + userFullName + "'."); + model.addAttribute("users", users); + return "user/user-list"; + } + + @GetMapping("/by-location/{userLocation}") + String getUsersByUserLocation(@PathVariable String userLocation, Model model) { + List users = userService.getAllUsersByLocation(userLocation); + if (users.isEmpty()) + model.addAttribute("message", "No users found for location '" + userLocation + "'."); + model.addAttribute("users", users); + return "user/user-list"; + } + + @GetMapping("/by-role/{userRole}") + public String getUsersByUserRole(@PathVariable String userRole, Model model) { + List users = userService.getAllUsersByRole(userRole); + if (users.isEmpty()) + model.addAttribute("message", "No results found for role '" + userRole + "'."); + model.addAttribute("users", users); + return "user/user-list"; + } + + @GetMapping("/by-role-location") + String getUsersByRoleAndLocation(@RequestParam String userRole, @RequestParam String userLocation, Model model) { + List users = userService.getAllUsersByRoleAndLocation(userRole, userLocation); + if (users.isEmpty()) + model.addAttribute("message", "No results found for role '" + userRole + "' and location '" + userLocation + "'."); + model.addAttribute("users", users); + return "user/user-list"; + } + + @GetMapping("/by-cat") + String getUsersByCatName(@RequestParam String catName, Model model) { + List users = userService.getAllUsersByCatName(catName); + if (users.isEmpty()) + model.addAttribute("message", "No results found for cat '" + catName + "'."); + model.addAttribute("users", users); + return "user/user-list"; + } + + @GetMapping("/by-search-term/{searchTerm}") + String getUsersByUserNameOrCatName(@PathVariable String searchTerm, Model model) { + List users = userService.getAllUsersByUserNameOrCatName(searchTerm); + if (users.isEmpty()) + model.addAttribute("message", "No results found for search term '" + searchTerm + "'."); + model.addAttribute("users", users); + return "user/user-list"; + } + + @GetMapping("/add") + String addUserForm(Model model) { + if (!model.containsAttribute("user")) { + model.addAttribute("user", new User()); + } + return "user/user-add"; + } + + @PostMapping("/add") + String addUserForm(@Valid @ModelAttribute User user, Model model, BindingResult bindingResult, RedirectAttributes redirectAttributes) { + if (bindingResult.hasErrors()) { + return "user/user-add"; + } + try { + userService.addUser(user); + redirectAttributes.addFlashAttribute("success", "Account created!"); + } catch (UserAlreadyExistsException ex) { + model.addAttribute("error", ex.getMessage()); + return "user/user-add"; + } + return "redirect:/users/profile/id/" + user.getUserId(); + } + + @GetMapping("/{userId}/edit") + public String showUpdateForm(@PathVariable Long userId, Model model) { + try { + User user = userService.getUserById(userId); + model.addAttribute("user", user); + return "user/user-update"; + } catch (NotFoundException ex) { + model.addAttribute("error", ex.getMessage()); + return "error-page"; + } + } + + @PutMapping("/{userId}") + public String updateUser(@PathVariable Long userId, @Valid @ModelAttribute User user, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) { + if (bindingResult.hasErrors()) { + return "user/user-update"; + } + try { + userService.updateUser(userId, user); + redirectAttributes.addFlashAttribute("update_success", "Details saved!"); + } catch (UserAlreadyExistsException ex) { + redirectAttributes.addFlashAttribute("error", ex.getMessage()); + return "redirect:/users/" + userId + "/edit"; + } + return "redirect:/users/" + userId + "/edit"; + } + + @PatchMapping("/{userId}") + public String updateUser(@PathVariable Long userId, @RequestParam Map updates, RedirectAttributes redirectAttributes, Model model) { + try { + User updatedUser = userService.updateUser(userId, updates); + redirectAttributes.addFlashAttribute("update_success", "Details saved!"); + } catch (UserAlreadyExistsException | NotFoundException ex) { + model.addAttribute("error", ex.getMessage()); + return "error-page"; + } + return "redirect:/users/" + userId + "/edit"; + } + + @DeleteMapping("/{userId}") + String deleteUser(@PathVariable Long userId, RedirectAttributes redirectAttributes, Model model) { + try { + userService.deleteUserById(userId); + redirectAttributes.addFlashAttribute("delete_success", "Account deleted!"); + } catch (NotFoundException ex) { + model.addAttribute("error", ex.getMessage()); + return "error-page"; + } + return "redirect:/"; + } +} diff --git a/src/main/java/org/example/springboot25/entities/User.java b/src/main/java/org/example/springboot25/entities/User.java index 7e804adf..85603ce6 100644 --- a/src/main/java/org/example/springboot25/entities/User.java +++ b/src/main/java/org/example/springboot25/entities/User.java @@ -17,10 +17,15 @@ public class User { private Long userId; @NotBlank + private String userFullName; + + @NotBlank + @Column(unique = true) private String userName; @Email @NotBlank + @Column(unique = true) private String userEmail; @NotBlank @@ -49,6 +54,14 @@ public Long getUserId() { return userId; } + public @NotBlank String getUserFullName() { + return userFullName; + } + + public void setUserFullName(String fullName) { + this.userFullName = fullName; + } + public String getUserName() { return userName; } diff --git a/src/main/java/org/example/springboot25/exceptions/GlobalViewExceptionHandler.java b/src/main/java/org/example/springboot25/exceptions/GlobalViewExceptionHandler.java new file mode 100644 index 00000000..75fe5bf8 --- /dev/null +++ b/src/main/java/org/example/springboot25/exceptions/GlobalViewExceptionHandler.java @@ -0,0 +1,25 @@ +package org.example.springboot25.exceptions; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +@ControllerAdvice +public class GlobalViewExceptionHandler { + + @ExceptionHandler(NotFoundException.class) + public ModelAndView handleNotFoundException(NotFoundException ex) { + ModelAndView mav = new ModelAndView("error-page"); + mav.addObject("error", ex.getMessage()); + return mav; + } + + @ExceptionHandler(UserAlreadyExistsException.class) + public ModelAndView handleUserAlreadyExistsException(UserAlreadyExistsException ex) { + ModelAndView mav = new ModelAndView("error-page"); + mav.addObject("error", ex.getMessage()); + return mav; + } +} diff --git a/src/main/java/org/example/springboot25/exceptions/UserAlreadyExistsException.java b/src/main/java/org/example/springboot25/exceptions/UserAlreadyExistsException.java new file mode 100644 index 00000000..7e534e05 --- /dev/null +++ b/src/main/java/org/example/springboot25/exceptions/UserAlreadyExistsException.java @@ -0,0 +1,7 @@ +package org.example.springboot25.exceptions; + +public class UserAlreadyExistsException extends RuntimeException { + public UserAlreadyExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/org/example/springboot25/repository/UserRepository.java b/src/main/java/org/example/springboot25/repository/UserRepository.java new file mode 100644 index 00000000..05f75116 --- /dev/null +++ b/src/main/java/org/example/springboot25/repository/UserRepository.java @@ -0,0 +1,41 @@ +package org.example.springboot25.repository; + +import org.example.springboot25.entities.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + boolean existsByUserName(String userName); + boolean existsByUserEmail(String userEmail); + + Optional findByUserName(String userName); + Optional findByUserEmail(String userEmail); + + @Query("SELECT u FROM User u WHERE u.userName LIKE %:userName%") + List findAllByUserName(String userName); + + @Query("SELECT u FROM User u WHERE u.userFullName LIKE %:userFullName%") + List findAllByUserFullName(String userFullName); + + @Query("SELECT u FROM User u WHERE u.userLocation = :userLocation") + List findAllByLocation(String userLocation); + + @Query("SELECT u FROM User u WHERE u.userRole = :userRole") + List findAllByRole(String userRole); + + @Query("SELECT u FROM User u WHERE u.userRole = :userRole AND u.userLocation = :userLocation") + List findAllByRoleAndLocation(String userRole, String userLocation); + + @Query("SELECT u FROM User u JOIN u.userCats c WHERE c.catName LIKE %:catName%") + List findAllUsersByCatName(String catName); + + @Query("SELECT DISTINCT u FROM User u LEFT JOIN u.userCats c " + + "WHERE u.userName LIKE %:searchTerm% OR c.catName LIKE %:searchTerm%") + List findUsersByUsernameOrCatName(@Param("searchTerm") String searchTerm); + + +} diff --git a/src/main/java/org/example/springboot25/service/UserService.java b/src/main/java/org/example/springboot25/service/UserService.java new file mode 100644 index 00000000..1aa22f0f --- /dev/null +++ b/src/main/java/org/example/springboot25/service/UserService.java @@ -0,0 +1,162 @@ +package org.example.springboot25.service; + +import jakarta.transaction.Transactional; +import org.example.springboot25.entities.User; +import org.example.springboot25.exceptions.NotFoundException; +import org.example.springboot25.exceptions.UserAlreadyExistsException; +import org.example.springboot25.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +@Transactional +public class UserService { + + private static final Logger log = LoggerFactory.getLogger(UserService.class); + private final UserRepository userRepository; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public List getAllUsers() { + log.info("Fetching all users"); + return userRepository.findAll(); + } + + public User getUserById(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("User with id " + userId + " not found")); + } + + public User getUserByUserName(String userName) { + return userRepository.findByUserName(userName) + .orElseThrow(() -> new NotFoundException("User with username " + userName + " not found")); + } + + public User getUserByEmail(String userEmail) { + return userRepository.findByUserEmail(userEmail) + .orElseThrow(() -> new NotFoundException("User with email " + userEmail + " not found")); + } + + public List getAllUsersByUserName(String userName) { + return userRepository.findAllByUserName(userName); + } + + public List getAllUsersByFullName(String fullName) { + return userRepository.findAllByUserFullName(fullName); + } + + public List getAllUsersByLocation(String userLocation) { + return userRepository.findAllByLocation(userLocation); + } + + public List getAllUsersByRole(String userRole) { + return userRepository.findAllByRole(userRole); + } + + public List getAllUsersByRoleAndLocation(String userRole, String userLocation) { + return userRepository.findAllByRoleAndLocation(userRole, userLocation); + } + + public List getAllUsersByCatName(String catName) { + return userRepository.findAllUsersByCatName(catName); + } + + public List getAllUsersByUserNameOrCatName(String searchTerm) { + return userRepository.findUsersByUsernameOrCatName(searchTerm); + } + + public User addUser(User user) { + if (userRepository.existsByUserEmail(user.getUserEmail())) + throw new UserAlreadyExistsException("Account with given email already exists."); + if (userRepository.existsByUserName(user.getUserName())) + throw new UserAlreadyExistsException("Username is taken."); + log.info("Creating new user: {}", user.getUserName()); + return userRepository.save(user); + } + + /** + * Full update of a user. + * Retrieves the existing user by id, updates all fields, and saves. + */ + public User updateUser(Long userId, User user) { + User oldUser = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("User with id " + userId + " not found.")); + // Check if the email belongs to a different user. + Optional userByEmail = userRepository.findByUserEmail(user.getUserEmail()); + if (userByEmail.isPresent() && !userByEmail.get().getUserId().equals(userId)) { + throw new UserAlreadyExistsException("Account with given email already exists."); + } + + // Check if the username belongs to a different user. + Optional userByName = userRepository.findByUserName(user.getUserName()); + if (userByName.isPresent() && !userByName.get().getUserId().equals(userId)) { + throw new UserAlreadyExistsException("Username is taken."); + } + log.info("Updating user: {}", user.getUserName()); + oldUser.setUserName(user.getUserName()); + oldUser.setUserFullName(user.getUserFullName()); + oldUser.setUserEmail(user.getUserEmail()); + oldUser.setUserLocation(user.getUserLocation()); + oldUser.setUserRole(user.getUserRole()); + oldUser.setUserAuthProvider(user.getUserAuthProvider()); + return userRepository.save(oldUser); + } + + /** + * Partially updates a user based on provided field changes. + */ + public User updateUser(Long userId, Map updates) { + User existingUser = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("User with id " + userId + " not found.")); + + if (updates.containsKey("userEmail")) { + String newEmail = updates.get("userEmail").toString(); + Optional userByEmail = userRepository.findByUserEmail(newEmail); + if (userByEmail.isPresent() && !userByEmail.get().getUserId().equals(userId)) { + throw new UserAlreadyExistsException("Account with given email already exists."); + } + } + + if (updates.containsKey("userName")) { + String newUserName = updates.get("userName").toString(); + Optional userByName = userRepository.findByUserName(newUserName); + if (userByName.isPresent() && !userByName.get().getUserId().equals(userId)) { + throw new UserAlreadyExistsException("Username is taken."); + } + } + + log.info("Partially updating user: {}", existingUser.getUserName()); + if (updates.containsKey("userFullName")) { + existingUser.setUserFullName((String) updates.get("userFullName")); + } + if (updates.containsKey("userName")) { + existingUser.setUserName((String) updates.get("userName")); + } + if (updates.containsKey("userEmail")) { + existingUser.setUserEmail((String) updates.get("userEmail")); + } + if (updates.containsKey("userLocation")) { + existingUser.setUserLocation((String) updates.get("userLocation")); + } + if (updates.containsKey("userRole")) { + existingUser.setUserRole((String) updates.get("userRole")); + } + if (updates.containsKey("userAuthProvider")) { + existingUser.setUserAuthProvider((String) updates.get("userAuthProvider")); + } + return userRepository.save(existingUser); + } + + + public void deleteUserById(Long userId) { + log.info("Deleting user with id: {}", userId + "."); + userRepository.deleteById(userId); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8738b7a4..30f6a5b4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,2 @@ spring.application.name=springboot25 - +spring.mvc.hiddenmethod.filter.enabled=true diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css new file mode 100644 index 00000000..a832acc8 --- /dev/null +++ b/src/main/resources/static/css/main.css @@ -0,0 +1,230 @@ +html, body { + margin: 0; + padding: 0; + height: 100%; +} + +body { + font-family: 'Montserrat', sans-serif; + text-align: center; + background: linear-gradient(to bottom, #fad9c4, rgb(227, 162, 156)) no-repeat fixed; + background-size: cover; +} + + +header { + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(10, 10, 10, 0.9); + padding: 1.5em 1em; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.header-left { + display: flex; + align-items: center; +} + +h1 { + text-align: left; + font-size: 45px; + margin: 0 10px 0 20px; + background-color: transparent; + color: #e79a6f; + text-shadow: 0 1px 2px rgba(255, 0, 0, 0.7), + 0 3px 6px rgba(255, 104, 15, 0.5), + 0 10px 20px rgba(255, 255, 255, 0.1); + cursor: default; +} + +.cat-emoji { + font-size: 1em; + text-shadow: none; + vertical-align: middle; + margin-right: 10px; +} + +.about-link { + font-size: 18px; + text-decoration: none; + color: #e79a6f; + margin-left: 20px; + font-weight: bold; +} + +.about-link:hover { + color: rgba(255, 255, 255, 0.3); +} + +.header-right { + margin-right: 20px; +} + +.lang-button { + background-color: transparent; + border: none; + outline: none; + border-radius: 20px; + padding: 5px 15px; + font-size: 16px; + color: #fffaef; + cursor: pointer; + transition: background-color 0.3s, color 0.3s; +} + +.lang-button:hover { + background-color: rgba(148, 148, 148, 0.3); + color: #fff; +} + +p { + cursor: default; +} + +.select-text { + color: rgba(85, 85, 85, 0.75); + font-weight: 700; + margin-top: 8%; +} + +.main-content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + min-height: calc(100vh - 120px); + text-align: center; +} + +.link-box { + background: rgba(255, 223, 209, 0.31); + border-radius: 10px; + padding: 40px; + width: 300px; + margin: auto; +} + +.link-box ul { + list-style: none; + padding: 0; + margin: 0; +} + +.link-box ul li { + margin-bottom: 20px; + font-size: 22px; + text-align: center; +} + +.link-box ul li:last-child { + margin-bottom: 0; +} + +a { + text-decoration: none; + color: #505050; + font-weight: 600; + text-shadow: none; +} + +a:hover { + color: #555; + cursor: pointer; +} + +a:active { + text-shadow: 0 1px 2px rgba(255, 0, 0, 0.3); +} + +.form-container { + max-width: 600px; + margin: 30px auto; + padding: 20px; + background-color: rgba(255, 255, 255, 0.4); + border-radius: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.form-row { + display: flex; + align-items: center; + margin-bottom: 15px; +} + +.form-row label { + width: 150px; + text-align: right; + margin-right: 10px; +} + +.form-row input { + width: 50%; + padding: 8px; + border: none; + border-radius: 5px; + background-color: #f9f9f9; + box-sizing: border-box; +} + +.form-row button { + margin-left: auto; + background-color: #dc6b24; + color: #fff; + border: none; + padding: 8px 16px; + border-radius: 5px; + cursor: pointer; +} + +.form-row button:active { + box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); + background-color: #f39051; + color: #000000; + padding: 8px 16px; +} + +.delete-button { + display: inline-block; /* Size to content */ + margin-top: 20px; /* Adds space above the button */ + background-color: #dc2424; + color: #fff; + border: none; + padding: 8px 16px; + border-radius: 5px; + cursor: pointer; +} + +.delete-button:active { + box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); + background-color: #f35151; + color: #000; +} + +.button-row { + position: relative; + display: flex; + justify-content: flex-end; + align-items: center; +} + +.success-message { + position: absolute; + left: 50%; + transform: translateX(-50%); + margin-top: 10px; + text-align: center; + text-shadow: 0 0 2px rgba(0, 0, 0, 0.1); + color: #17c41a; +} + +.error-message { + position: absolute; + left: 50%; + transform: translateX(-50%); + margin-top: 10px; + font-size: smaller; + text-align: center; + text-shadow: 0 0 2px rgba(0, 0, 0, 0.1); + color: #c41717; +} diff --git a/src/main/resources/templates/error-page.html b/src/main/resources/templates/error-page.html new file mode 100644 index 00000000..87c5b8be --- /dev/null +++ b/src/main/resources/templates/error-page.html @@ -0,0 +1,10 @@ + + + + + Catinder - Error + + + + + diff --git a/src/main/resources/templates/fragments/header.html b/src/main/resources/templates/fragments/header.html new file mode 100644 index 00000000..a27889fd --- /dev/null +++ b/src/main/resources/templates/fragments/header.html @@ -0,0 +1,14 @@ + + +
+
+

🐱Catinder

+ About +
+
+ +
+
+ diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 0360eb46..a0151b47 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -4,161 +4,14 @@ Catinder - Home - + -
-
-

🐱Catinder

- About -
-
- -
-
+
+

Select a section to explore: