Skip to content

Commit

Permalink
Photo upload on user edit working fine, listing with photos working f…
Browse files Browse the repository at this point in the history
…ine, still not able to preview images while editing
  • Loading branch information
surajcm committed Mar 2, 2024
1 parent 37893bc commit 0d5d756
Show file tree
Hide file tree
Showing 15 changed files with 173 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ build/
!**/src/main/**
!**/src/test/**
logs/
user-photos/


### STS ###
.apt_generated
.classpath
Expand Down
1 change: 1 addition & 0 deletions config/checkstyle/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ WHAT AND WHY CHECKS SUPPRESSED
<suppress files="ChallengeService.java" checks="MethodLength"/>
<suppress files="SecurityConfig.java" checks="MethodLength"/>
<suppress files="RegistrationController.java" checks="MethodLength"/>
<suppress files="UserController.java" checks="MethodLength"/>
</suppressions>
2 changes: 1 addition & 1 deletion gradle/staticCodeAnalysis.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.28
minimum = 0.27
}
}
}
Expand Down
16 changes: 14 additions & 2 deletions src/main/java/com/quiz/darkhold/init/AppMvcConfig.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.quiz.darkhold.init;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Component
import java.nio.file.Paths;

@Configuration
public class AppMvcConfig implements WebMvcConfigurer {
@Autowired
AppMvcInterceptor appMvcInterceptor;
Expand All @@ -14,4 +17,13 @@ public class AppMvcConfig implements WebMvcConfigurer {
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(appMvcInterceptor);
}

@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
var dirName = "user-photos";
var userPhotosDirName = Paths.get("user-photos");
var userPhotosPath = userPhotosDirName.toFile().getAbsolutePath();
registry.addResourceHandler("/" + dirName + "/**")
.addResourceLocations("file:" + userPhotosPath + "/");
}
}
43 changes: 43 additions & 0 deletions src/main/java/com/quiz/darkhold/init/FileUploadUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.quiz.darkhold.init;

import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class FileUploadUtil {
public static void saveFile(final String uploadDir,
final String fileName,
final MultipartFile multipartFile) throws IOException {
final Path uploadPath = Paths.get(uploadDir);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
try (final InputStream inputStream = multipartFile.getInputStream()) {
final Path filePath = uploadPath.resolve(fileName);
Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
} catch (final IOException ioe) {
throw new IOException("Could not save image file: " + fileName, ioe);
}
}

public static void cleanDir(final String dir) {
try {
Files.list(Paths.get(dir)).forEach(file -> {
if (!Files.isDirectory(file)) {
try {
Files.delete(file);
} catch (final IOException ioe) {
System.out.println("Could not delete file: " + file);
}
}
});
} catch (final IOException ioe) {
System.out.println("Could not list directory: " + dir);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package com.quiz.darkhold.user.controller;

import com.quiz.darkhold.init.FileUploadUtil;
import com.quiz.darkhold.user.entity.User;
import com.quiz.darkhold.user.exception.UserNotFoundException;
import com.quiz.darkhold.user.service.UserService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.io.IOException;

@Controller
public class UserController {
private final Logger logger = LogManager.getLogger(UserController.class);
Expand Down Expand Up @@ -47,9 +53,22 @@ public String createUser(final Model model) {
}

@PostMapping("/users/save")
public String saveUser(final User user, final RedirectAttributes redirectAttributes) {
public String saveUser(final User user, final RedirectAttributes redirectAttributes,
@RequestParam("image") final MultipartFile multipartFile) throws IOException {
logger.info("Into the saveUser method, user is {}", user);
userService.save(user);
if (multipartFile != null && !multipartFile.isEmpty()) {
var fileName = StringUtils.cleanPath(multipartFile.getOriginalFilename());
user.setPhoto(fileName);
var savedUser = userService.save(user);
var uploadDir = "user-photos/" + savedUser.getId();
FileUploadUtil.cleanDir(uploadDir);
FileUploadUtil.saveFile(uploadDir, fileName, multipartFile);
} else {
if (user.getPhoto().isEmpty()) {
user.setPhoto(null);
}
userService.save(user);
}
redirectAttributes.addFlashAttribute("message", "The user has been saved successfully");
return "redirect:/userManagement";
}
Expand Down Expand Up @@ -82,5 +101,17 @@ public String deleteUser(@PathVariable(name = "id") final Long id,
}
return "redirect:/userManagement";
}

@GetMapping("/users/{id}/enabled/{status}")
public String updateUserEnabledStatus(@PathVariable("id") final Long id,
@PathVariable("status") final boolean enabled,
final RedirectAttributes redirectAttributes) {
logger.info("Into the updateUserEnabledStatus method, id is {}, status is {}", id, enabled);
userService.updateUserEnabledStatus(id, enabled);
var status = enabled ? "enabled" : "disabled";
var message = "The user ID " + id + " has been " + status + " successfully";
redirectAttributes.addFlashAttribute("message", message);
return "redirect:/userManagement";
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.quiz.darkhold.user.controller;

import com.quiz.darkhold.user.service.UserService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -9,12 +11,15 @@
public class UserRestController {
private final UserService userService;

private final Logger logger = LogManager.getLogger(UserRestController.class);

public UserRestController(final UserService userService) {
this.userService = userService;
}

@PostMapping("/users/check_email")
public String checkDuplicateEmail(@Param("id") final Long id, @Param("email") final String email) {
logger.info("Checking email for id: {} and email: {}", id, email);
return userService.isEmailUnique(id, email) ? "OK" : "DUPLICATED";
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/quiz/darkhold/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -128,4 +130,12 @@ public String toString() {
.add("roles=" + roles)
.toString();
}

@Transient
public String getPhotosImagePath() {
if (id == null || photo == null) {
return "/images/default-user.png";
}
return "/user-photos/" + this.id + "/" + this.photo;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

import com.quiz.darkhold.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);

Long countById(Long id);

@Query("UPDATE User u SET u.enabled = ?2 WHERE u.id = ?1")
@Modifying
void updateEnabledStatus(Long id, boolean enabled);
}


Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.util.List;

public interface UserService {
void save(User user);
User save(User user);

User findByUsername(String username);

Expand All @@ -20,5 +20,7 @@ public interface UserService {
User get(Long id) throws UserNotFoundException;

void delete(final Long id) throws UserNotFoundException;

void updateUserEnabledStatus(final Long id, final boolean enabled);
}

13 changes: 10 additions & 3 deletions src/main/java/com/quiz/darkhold/user/service/UserServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import com.quiz.darkhold.user.exception.UserNotFoundException;
import com.quiz.darkhold.user.repository.RoleRepository;
import com.quiz.darkhold.user.repository.UserRepository;
import jakarta.transaction.Transactional;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@Transactional
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
Expand All @@ -25,14 +27,14 @@ public UserServiceImpl(final UserRepository userRepository,
}

@Override
public void save(final User user) {
public User save(final User user) {
if (isUpdatingUser(user)) {
var existingUser = findExistingUser(user.getId());
user.setPassword(getPassword(user, existingUser));
} else {
user.setPassword(encodePassword(user.getPassword()));
}
userRepository.save(user);
return userRepository.save(user);
}

private boolean isUpdatingUser(final User user) {
Expand Down Expand Up @@ -72,7 +74,7 @@ public Boolean isEmailUnique(final Long id, final String email) {
if (userByEmail == null) {
return true;
}
return isCreatingNew(id) ? userByEmail == null : !userByEmail.getId().equals(id);
return isCreatingNew(id) ? userByEmail == null : userByEmail.getId().equals(id);
}

private boolean isCreatingNew(final Long id) {
Expand All @@ -93,4 +95,9 @@ public void delete(final Long id) throws UserNotFoundException {
}
userRepository.deleteById(id);
}

@Override
public void updateUserEnabledStatus(final Long id, final boolean enabled) {
userRepository.updateEnabledStatus(id, enabled);
}
}
3 changes: 2 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.Ph
spring.h2.console.enabled=true
#spring.datasource.url=jdbc:h2:file:/tmp/db/darkhold
#spring.datasource.driver-class-name=org.h2.Driver
spring.flyway.enabled=false
spring.flyway.enabled=false
#spring.mvc.pathmatch.matching-strategy=ant_path_matcher
Binary file added src/main/resources/static/images/default-user.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 34 additions & 2 deletions src/main/resources/templates/user/user_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
<h3 class="panel-title">[[${pageTitle}]]</h3>
</div>
</div>
<form th:action="@{/users/save}" method="post" th:object="${userForm}" onsubmit="return checkEmailUnique(this);">
<form th:action="@{/users/save}" method="post" th:object="${userForm}"
enctype="multipart/form-data"
onsubmit="return checkEmailUnique(this);">
<input type="hidden" th:field="*{id}" />
<div class="border border-secondary rounded p-3">
<div class="input-group mb-3">
Expand Down Expand Up @@ -71,6 +73,15 @@ <h3 class="panel-title">[[${pageTitle}]]</h3>
<input type="checkbox" th:field="*{enabled}" />
</div>
</div>
<div class="input-group mb-3">
<label class="col-sm-4 col-form-label">Photos : </label>
<div class="col-sm-8">
<input type="hidden" th:field="*{photo}" />
<input type="file" id="fileImage" name="image" accept="image/png, image/jpeg" class="mb-2"/>
<img id="thumbnail" alt="Photos preview" th:src="@{${userForm.photosImagePath}}"
onchange="changeThumbnail(this);" class="img-fluid"/>
</div>
</div>
<div class="text-center">
<input type="submit" value="Save" class="btn btn-primary m-3" />
<input type="button" value="Cancel" id="cancelButton" onclick="toUsers()" class="btn border-secondary" />
Expand Down Expand Up @@ -107,7 +118,7 @@ <h4 class="modal-title" id="modalTitle">Warning</h4>
const xhr = new XMLHttpRequest();
xhr.open("POST", "/users/check_email", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("email=" + encodeURIComponent(email) + "&userId=" + userId);
xhr.send("email=" + encodeURIComponent(email) + "&id=" + userId);
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
// The request has been processed successfully
Expand All @@ -130,6 +141,27 @@ <h4 class="modal-title" id="modalTitle">Warning</h4>
const myModal = new bootstrap.Modal(document.getElementById('modalDialog'));
myModal.show();
}

function changeThumbnail(input) {
const fileSize = input.files[0].size;
console.log("File size : " + fileSize);
if (fileSize > 1048576) {
this.setCustomValidity("Please select a file with a size less than 1 MB.");
this.reportValidity();
} else {
this.setCustomValidity("");
showImageThumbnail(input);
}
}

function showImageThumbnail(fileInput) {
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = function (e) {
document.getElementById('thumbnail').setAttribute('src', e.target.result);
};
reader.readAsDataURL(file);
}
/*]]>*/
</script>
</body>
Expand Down
11 changes: 8 additions & 3 deletions src/main/resources/templates/user/usermanagement.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,19 @@ <h2>Manage Users</h2>
<tbody>
<tr th:each="user: ${listusers}">
<td>[[${user.id}]]</td>
<td><span class="fas fa-portrait fa-3x icon-silver"></span></td>
<td>
<span th:if="${user.photo == null}" class="fas fa-portrait fa-3x icon-silver"></span>
<img th:if="${user.photo != null}" th:src="@{${user.photosImagePath}}" style="width: 100px" />
</td>
<td>[[${user.email}]]</td>
<td>[[${user.firstName}]]</td>
<td>[[${user.lastName}]]</td>
<td>[[${user.roles}]]</td>
<td>
<a th:if="${user.enabled == true}" class="fas fa-check-circle fa-2x icon-green" href=""></a>
<a th:if="${user.enabled == false}" class="fas fa-check-circle fa-2x icon-dark" href=""></a>
<a th:if="${user.enabled == true}" class="fas fa-check-circle fa-2x icon-green"
th:href="@{'/users/' + ${user.id} + '/enabled/false'}" title="Disable this user"></a>
<a th:if="${user.enabled == false}" class="fas fa-circle fa-2x icon-dark"
th:href="@{'/users/' + ${user.id} + '/enabled/true'}" title="Enable this user"></a>
</td>
<td>
<a class="fas fa-edit fa-2x icon-green" th:href="@{'/user/edit/' + ${user.id}}" title="Edit this user"></a>
Expand Down

0 comments on commit 0d5d756

Please sign in to comment.