Skip to content
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

[KTP-74] Implement User Profile & Edit Profile #51

Merged
merged 18 commits into from
Jan 1, 2025
Merged
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
8 changes: 7 additions & 1 deletion backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,14 @@
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>

</dependencies>
</dependencies>
<build>
<plugins>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ public ResponseEntity<Map<String, String>> signup(@RequestBody @Valid SignupRequ
user.setPassword(passwordEncoder.encode(signupRequest.getPassword()));
user.setEmail(signupRequest.getEmail());
user.setRole("USER");

if (signupRequest.getGender() != null && !signupRequest.getGender().isEmpty())
user.setGender(signupRequest.getGender());
if (signupRequest.getBirthdate() != null)
user.setBirthdate(signupRequest.getBirthdate());
userRepository.save(user);

return ResponseEntity.ok(Map.of("message", "User registered successfully!"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,68 @@
package com.csed.knowtopia.controller;

import com.csed.knowtopia.dto.UserDTO;
import com.csed.knowtopia.dto.UserProfileDTO;
import com.csed.knowtopia.dto.response.AuthResponse;
import com.csed.knowtopia.entity.User;
import com.csed.knowtopia.service.CustomUserPrincipal;
import com.csed.knowtopia.service.JwtUtil;
import com.csed.knowtopia.service.UserService;
import io.jsonwebtoken.Jwt;

import org.apache.coyote.Response;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
private final JwtUtil jwtUtil;

public UserController(UserService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.jwtUtil = jwtUtil;
}

@GetMapping("/profile")
public ResponseEntity<String> userProfile() {
return ResponseEntity.ok("Welcome, User! This is your profile.");
public ResponseEntity<UserProfileDTO> getUserProfile(@AuthenticationPrincipal CustomUserPrincipal principal) {
User currentUser = principal.getUser();
UserProfileDTO userProfile = new UserProfileDTO();
userProfile.setUsername(currentUser.getUsername());
userProfile.setEmail(currentUser.getEmail());
userProfile.setRole(currentUser.getRole());
userProfile.setBirthdate(currentUser.getBirthdate());
userProfile.setGender(currentUser.getGender());
return ResponseEntity.ok(userProfile);
}
@PostMapping("/check-username")
public ResponseEntity<Boolean> checkIfUsernameExists(@RequestBody String username) {
return ResponseEntity.ok(userService.isExistingUsername(username));
}

@PutMapping("/profile/edit")
public ResponseEntity<AuthResponse> editUserProfile(@AuthenticationPrincipal CustomUserPrincipal principal, @RequestBody UserProfileDTO userProfile) {
User currentUser = principal.getUser();
currentUser.setUsername(userProfile.getUsername());
currentUser.setEmail(userProfile.getEmail());
if (userProfile.getBirthdate() != null)
currentUser.setBirthdate(userProfile.getBirthdate());
if (userProfile.getGender() != null && !userProfile.getGender().isEmpty())
currentUser.setGender(userProfile.getGender());
userService.updateUser(currentUser);
System.out.println("User updated");
String jwt = jwtUtil.generateToken(currentUser.getUsername());
return ResponseEntity.ok(new AuthResponse(jwt));
}
@GetMapping("/info")
public ResponseEntity<UserDTO> getUserInfo(@AuthenticationPrincipal CustomUserPrincipal principal) {
User user_entity = principal.getUser();
Expand Down
14 changes: 14 additions & 0 deletions backend/src/main/java/com/csed/knowtopia/dto/UserProfileDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.csed.knowtopia.dto;

import java.time.LocalDate;

import lombok.Data;

@Data
public class UserProfileDTO {
private String username;
private String email;
private LocalDate birthdate;
private String gender;
private String role;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package com.csed.knowtopia.dto.request;

import java.time.LocalDate;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class SignupRequest {
@Schema(description = "Username for the account", example = "john_doe")
@NotBlank(message = "Username is required")
Expand All @@ -26,29 +34,11 @@ public class SignupRequest {
@Size(max = 50, message = "Email must not exceed 50 characters")
private String email;

// Getters and setters

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getEmail() {
return email;
}
@Schema(description = "Gender of the user", example = "Male")
@Pattern(regexp = "^(Male|Female)$", message = "Gender must be either Male or Female")
private String gender;

public void setEmail(String email) {
this.email = email;
}
@Schema(description = "Birthdate of the user", example = "2000-01-01")
@Past(message = "Birthdate must be in the past")
private LocalDate birthdate;
}
26 changes: 25 additions & 1 deletion backend/src/main/java/com/csed/knowtopia/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import net.minidev.json.annotate.JsonIgnore;

import java.time.LocalDate;
import java.util.List;

import org.springframework.context.annotation.Lazy;

@Entity
@Table(name = "users")
public class User {
Expand All @@ -29,6 +33,12 @@ public class User {
@Column(name = "role", nullable = false)
private String role;

@Column(name = "gender", nullable = true)
private String gender;

@Column(name = "birthdate", nullable = true)
private LocalDate birthdate;

public String getProvider() {
return provider;
}
Expand All @@ -40,7 +50,7 @@ public void setProvider(String provider) {
@Column(name = "provider", nullable = true)
private String provider;

@OneToMany(mappedBy = "instructor")
@OneToMany(fetch = FetchType.LAZY, mappedBy = "instructor")
private List<Course> courses;


Expand All @@ -52,11 +62,17 @@ public void setId(Long id) {
this.id = id;
}

public void setGender(String gender) {
this.gender = gender;
}

public void setUsername(String username) {
this.username = username;
}

public String getGender() {
return gender;
}

public String getUsername() {
return username;
Expand Down Expand Up @@ -86,6 +102,14 @@ public void setRole(String role) {
this.role = role;
}

public void setBirthdate(LocalDate birthdate) {
this.birthdate = birthdate;
}

public LocalDate getBirthdate() {
return birthdate;
}

public List<Course> getCourses() {
return courses;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.csed.knowtopia.service;

import com.csed.knowtopia.dto.UserProfileDTO;
import com.csed.knowtopia.entity.User;
import com.csed.knowtopia.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -22,6 +23,11 @@ public class UserService {

private static final String GOOGLE_CLIENT_ID = "1014303888663-qicnvdinpr85jnilqbs3ol52n3hjtjj2.apps.googleusercontent.com";

public UserService() {
}
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

public User findOrCreateUser(String email, String name, String provider) {
Optional<User> existingUser = userRepository.findByEmail(email);
Expand All @@ -43,7 +49,7 @@ public User findOrCreateUser(String email, String name, String provider) {

return userRepository.save(newUser);
}
public User findOrCreateUserFromGoogleToken(String token) throws Exception {
public User findOrCreateUserFromGoogleToken(String token) throws Exception {
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(
GoogleNetHttpTransport.newTrustedTransport(), jsonFactory)
Expand All @@ -62,4 +68,11 @@ public User findOrCreateUserFromGoogleToken(String token) throws Exception {
return findOrCreateUser(email, name, "google");
}

public void updateUser(User newUser) {
userRepository.save(newUser);
}
public boolean isExistingUsername(String username) {
return userRepository.findByUsername(username).isPresent();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.csed.knowtopia.controller;

import com.csed.knowtopia.dto.UserProfileDTO;
import com.csed.knowtopia.dto.response.AuthResponse;
import com.csed.knowtopia.entity.User;
import com.csed.knowtopia.repository.UserRepository;
import com.csed.knowtopia.service.CustomUserPrincipal;
import com.csed.knowtopia.service.JwtUtil;
import com.csed.knowtopia.service.UserService;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.test.context.support.TestExecutionEvent;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.time.LocalDate;

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private UserService userService;

@Autowired
private WebApplicationContext context;

private User mockUser;

@Autowired
private UserRepository userRepository;

@BeforeEach
void setUp() {
// Setup MockMvc with security configuration
this.mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
if (userRepository.findByUsername("mockUser").isPresent()) {
userRepository.delete(userRepository.findByUsername("mockUser").get());
}
// Create a mock user to simulate the logged-in user
mockUser = new User();
mockUser.setUsername("mockUser");
mockUser.setEmail("[email protected]");
mockUser.setRole("USER");
mockUser.setBirthdate(LocalDate.parse("2000-01-01"));
mockUser.setGender("Male");
mockUser.setPassword("TestPassword12@3");
userRepository.save(mockUser);
}

@Test
@WithUserDetails(value = "mockUser", setupBefore = TestExecutionEvent.TEST_EXECUTION)
void testGetUserProfile() throws Exception {
mockMvc.perform(get("/api/user/profile"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value(mockUser.getUsername()))
.andExpect(jsonPath("$.email").value(mockUser.getEmail()))
.andExpect(jsonPath("$.role").value(mockUser.getRole()))
.andExpect(jsonPath("$.birthdate").value(mockUser.getBirthdate().toString()))
.andExpect(jsonPath("$.gender").value(mockUser.getGender()));
}

@Test
@WithUserDetails(value = "mockUser", setupBefore = TestExecutionEvent.TEST_EXECUTION)
void testCheckIfUsernameExists_UsernameDoesNotExist() throws Exception {
String username = "non$xi$tinG";

mockMvc.perform(post("/api/user/check-username")
.contentType(MediaType.APPLICATION_JSON)
.content("\"" + username + "\""))
.andExpect(status().isOk())
.andExpect(content().string("false"));
}

@Test
@WithUserDetails(value = "mockUser", setupBefore = TestExecutionEvent.TEST_EXECUTION)
void testEditUserProfile() throws Exception {
mockMvc.perform(put("/api/user/profile/edit")
.contentType(MediaType.APPLICATION_JSON)
.content("{"
+ "\"username\": \"updateduser\","
+ "\"email\": \"[email protected]\","
+ "\"birthdate\": \"1999-12-31\","
+ "\"gender\": \"Female\""
+ "}"))
.andExpect(status().isOk());
}
}
Loading
Loading