diff --git a/README.md b/README.md index 3a45c1a..bd9162d 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,78 @@ Flyway is used to manage database migrations. The SQL scripts are located in `sr - `id`: The ID of the user to reactivate - **Response:** A `ResponseDto` object containing the result of the operation. +
+ Get user profile + +- **URL:** `/api/users/profile/{userId}` +- **Method:** `GET` +- **Description:** Retrieve the profile information for a specific user. +- **Path Parameters:** + - `id` (required): The ID of the user whose profile is to be retrieved. + - **Response:** + - **Status Code:** `200 OK` + - **Body:** + ```json + { + "status": "OK", + "success": true, + "data": { + "id": 160, + "firstName": "Teddy", + "lastName": "Johnson", + "title": null, + "description": null, + "hourlyRate": null, + "location": null + }, + "error": null + } + ``` +
+
+ Update user profile + +- **URL:** `/api/users/profile/{id}` +- **Method:** `PUT` +- **Description:** Update the profile information for a specific user. +- **Path Parameters:** + - `id` (required): The ID of the user whose currently logged in. + - **Request Body:** + - **Content-Type:** `application/json` + - **Body Example:** + ```json + { + "id": 160, + "firstName": "string", + "lastName": "string", + "title": "string", + "description": "string", + "hourlyRate": 0, + "location": "string" + } + ``` +- **Response:** + - **Status Code:** `200 OK` + - **Body Example:** + ```json + { + "status": "OK", + "success": true, + "data": { + "id": 160, + "firstName": "string", + "lastName": "string", + "title": "string", + "description": "string", + "hourlyRate": 0, + "location": "string" + }, + "error": null + } + + ``` + +
### Password Management diff --git a/src/main/java/com/activecourses/upwork/UpworkApplication.java b/src/main/java/com/activecourses/upwork/UpworkApplication.java index 96ca35d..07b656c 100644 --- a/src/main/java/com/activecourses/upwork/UpworkApplication.java +++ b/src/main/java/com/activecourses/upwork/UpworkApplication.java @@ -6,6 +6,7 @@ @SpringBootApplication @EnableJpaAuditing + public class UpworkApplication { public static void main(String[] args) { diff --git a/src/main/java/com/activecourses/upwork/controller/auth/AuthController.java b/src/main/java/com/activecourses/upwork/controller/auth/AuthController.java index 73ece45..9de71dc 100644 --- a/src/main/java/com/activecourses/upwork/controller/auth/AuthController.java +++ b/src/main/java/com/activecourses/upwork/controller/auth/AuthController.java @@ -50,7 +50,7 @@ public ResponseEntity registerUser(@Valid @RequestBody Registration description = "Login", security = @SecurityRequirement(name = "") ) - @PostMapping("login") + @PostMapping("login") public ResponseEntity login(@Valid @RequestBody LoginRequestDto loginRequestDto) { ResponseDto responseDto = authService.login(loginRequestDto); Map cookies = (Map) responseDto.getData(); diff --git a/src/main/java/com/activecourses/upwork/controller/auth/TokenController.java b/src/main/java/com/activecourses/upwork/controller/auth/TokenController.java index 0e74b65..265ecc7 100644 --- a/src/main/java/com/activecourses/upwork/controller/auth/TokenController.java +++ b/src/main/java/com/activecourses/upwork/controller/auth/TokenController.java @@ -4,7 +4,7 @@ import com.activecourses.upwork.exception.TokenRefreshException; import com.activecourses.upwork.model.User; import com.activecourses.upwork.service.authentication.AuthService; -import com.activecourses.upwork.service.authentication.RefreshTokenService; +import com.activecourses.upwork.service.RefreshTokenService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/src/main/java/com/activecourses/upwork/controller/user/UserController.java b/src/main/java/com/activecourses/upwork/controller/user/UserController.java index e076e50..f96e565 100644 --- a/src/main/java/com/activecourses/upwork/controller/user/UserController.java +++ b/src/main/java/com/activecourses/upwork/controller/user/UserController.java @@ -1,30 +1,30 @@ package com.activecourses.upwork.controller.user; import com.activecourses.upwork.dto.ResponseDto; +import com.activecourses.upwork.dto.user.UserProfileDto; import com.activecourses.upwork.dto.user.UserResponseDto; +import com.activecourses.upwork.service.user.UserProfileService; import com.activecourses.upwork.service.user.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - +import org.springframework.web.bind.annotation.*; +@Tag(name = "User", description = "User API") @RestController -@RequestMapping("/api/users") @RequiredArgsConstructor -@Tag(name = "User", description = "User API") +@RequestMapping("/api/users/") public class UserController { - private final UserService userService; + private final UserProfileService userProfileService; // TODO: update it so than it can be sorted by multiple fields @Operation(summary = "Get all users", @@ -54,4 +54,37 @@ public ResponseEntity getAllUsers( .status(HttpStatus.OK) .build()); } -} \ No newline at end of file + + @Operation(summary = "Get User Profile", + description = "Retrieve the profile information of the user specified by the userId.") + @GetMapping("/profile/{userId}") + public ResponseEntity getUserProfile(@PathVariable int userId, HttpServletRequest httpRequest) { + try { + return userProfileService.getUserProfile(userId); + } catch (Exception e) { + return ResponseEntity.badRequest().body(ResponseDto.builder() + .status(HttpStatus.BAD_REQUEST) + .success(false) + .data(e.getMessage()) + .build()); + } + } + + @Operation(summary = "Update User Profile", + description = "Update the profile information of the user specified by the userId") + @PutMapping("/profile/{userId}") + public ResponseEntity updateUserProfile( + @PathVariable int userId, + @RequestBody @Valid UserProfileDto updateRequest) { + try { + return userProfileService.UpdateUserProfile(userId,updateRequest); + } catch (Exception e) { + return ResponseEntity.badRequest().body(ResponseDto.builder() + .status(HttpStatus.BAD_REQUEST) + .success(false) + .data(e.getMessage()) + .build()); + } + + } +} diff --git a/src/main/java/com/activecourses/upwork/dto/user/UserProfileDto.java b/src/main/java/com/activecourses/upwork/dto/user/UserProfileDto.java new file mode 100644 index 0000000..43cb4a9 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/dto/user/UserProfileDto.java @@ -0,0 +1,27 @@ +package com.activecourses.upwork.dto.user; + +import jakarta.persistence.Id; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Builder +public class UserProfileDto { + @Id + private int id; + @NotBlank(message = "First Name is required") + private String firstName; + @NotBlank(message = "Last Name is required") + private String lastName; + + private String title; + + private String description; + + private BigDecimal hourlyRate; + + private String location; +} diff --git a/src/main/java/com/activecourses/upwork/dto/user/UserResponseDto.java b/src/main/java/com/activecourses/upwork/dto/user/UserResponseDto.java index f93d0cb..1a2a506 100644 --- a/src/main/java/com/activecourses/upwork/dto/user/UserResponseDto.java +++ b/src/main/java/com/activecourses/upwork/dto/user/UserResponseDto.java @@ -1,12 +1,14 @@ package com.activecourses.upwork.dto.user; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data +@Builder @AllArgsConstructor @NoArgsConstructor public class UserResponseDto { diff --git a/src/main/java/com/activecourses/upwork/mapper/user/UserProfileDtoMapper.java b/src/main/java/com/activecourses/upwork/mapper/user/UserProfileDtoMapper.java new file mode 100644 index 0000000..d4de4d8 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/mapper/user/UserProfileDtoMapper.java @@ -0,0 +1,30 @@ +package com.activecourses.upwork.mapper.user; + +import com.activecourses.upwork.dto.user.UserProfileDto; +import com.activecourses.upwork.mapper.Mapper; +import com.activecourses.upwork.model.User; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@AllArgsConstructor +public class UserProfileDtoMapper implements Mapper { + @Override + public UserProfileDto mapTo(User user) { + return UserProfileDto.builder() + .id(user.getId()) + .title(user.getUserProfile().getTitle()) + .description(user.getUserProfile().getDescription()) + .hourlyRate(user.getUserProfile().getHourlyRate()) + .location(user.getUserProfile().getLocation()) + .firstName(user.getFirstName()) + .lastName(user.getLastName()) + .build(); + } + + @Override + public User mapFrom(UserProfileDto userProfileDto) { + return null; + } + +} diff --git a/src/main/java/com/activecourses/upwork/model/User.java b/src/main/java/com/activecourses/upwork/model/User.java index 9c83263..7b310db 100644 --- a/src/main/java/com/activecourses/upwork/model/User.java +++ b/src/main/java/com/activecourses/upwork/model/User.java @@ -66,7 +66,8 @@ public class User implements UserDetails, Principal { @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, optional = true) private RefreshToken refreshToken; - + @OneToOne(mappedBy = "user",cascade = CascadeType.ALL) + private UserProfile userProfile; @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "user_roles", diff --git a/src/main/java/com/activecourses/upwork/model/UserProfile.java b/src/main/java/com/activecourses/upwork/model/UserProfile.java new file mode 100644 index 0000000..7b8151c --- /dev/null +++ b/src/main/java/com/activecourses/upwork/model/UserProfile.java @@ -0,0 +1,38 @@ +package com.activecourses.upwork.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.math.BigDecimal; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Builder +@EntityListeners(AuditingEntityListener.class) +@Table(name = "user_profiles") +public class UserProfile { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer profileId; + + private String title; + + private String description; + + @Column(precision = 19, scale = 2) // Example precision and scale + private BigDecimal hourlyRate; + + + private String location; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "user_id", unique = true) + private User user; + +} diff --git a/src/main/java/com/activecourses/upwork/service/authentication/RefreshTokenService.java b/src/main/java/com/activecourses/upwork/service/RefreshTokenService.java similarity index 96% rename from src/main/java/com/activecourses/upwork/service/authentication/RefreshTokenService.java rename to src/main/java/com/activecourses/upwork/service/RefreshTokenService.java index 0b315c6..7485161 100644 --- a/src/main/java/com/activecourses/upwork/service/authentication/RefreshTokenService.java +++ b/src/main/java/com/activecourses/upwork/service/RefreshTokenService.java @@ -1,4 +1,4 @@ -package com.activecourses.upwork.service.authentication; +package com.activecourses.upwork.service; import com.activecourses.upwork.config.security.jwt.JwtService; import com.activecourses.upwork.dto.ResponseDto; @@ -78,6 +78,9 @@ public ResponseEntity refreshToken(HttpServletRequest request) { private String getRefreshTokenFromRequest(HttpServletRequest request) { return jwtService.getJwtRefreshFromCookies(request); } + private String getTokenFromRequest(HttpServletRequest request) { + return jwtService.getJwtFromCookies(request); + } private RefreshToken getRefreshTokenEntity(String refreshToken) { return findByToken(refreshToken) diff --git a/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java b/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java index 914959b..218e245 100644 --- a/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java @@ -8,9 +8,11 @@ import com.activecourses.upwork.mapper.Mapper; import com.activecourses.upwork.model.RefreshToken; import com.activecourses.upwork.model.User; +import com.activecourses.upwork.model.UserProfile; import com.activecourses.upwork.repository.user.UserRepository; import com.activecourses.upwork.config.security.jwt.JwtService; +import com.activecourses.upwork.service.RefreshTokenService; import lombok.RequiredArgsConstructor; import java.util.Map; @@ -49,6 +51,7 @@ public class AuthServiceImpl implements AuthService { public RegistrationResponseDto registerUser(RegistrationRequestDto registrationRequestDto) { User user = userMapper.mapFrom(registrationRequestDto); user.setPassword(passwordEncoder.encode(user.getPassword())); + user.setUserProfile(new UserProfile()); userRepository.save(user); return RegistrationResponseDto diff --git a/src/main/java/com/activecourses/upwork/service/user/ImplUserProfileService.java b/src/main/java/com/activecourses/upwork/service/user/ImplUserProfileService.java new file mode 100644 index 0000000..6c34eb4 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/service/user/ImplUserProfileService.java @@ -0,0 +1,73 @@ +package com.activecourses.upwork.service.user; + +import com.activecourses.upwork.dto.ResponseDto; +import com.activecourses.upwork.dto.user.UserProfileDto; +import com.activecourses.upwork.mapper.Mapper; +import com.activecourses.upwork.model.User; +import com.activecourses.upwork.model.UserProfile; +import com.activecourses.upwork.repository.user.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ImplUserProfileService implements UserProfileService{ + private final UserRepository userRepository; + private final Mapper userProfileMapper; + @Override + public ResponseEntity getUserProfile(int id) { + User user = userRepository.findById(id). + orElseThrow(() -> new UsernameNotFoundException("user Not Found")); + UserProfileDto userProfileDto = userProfileMapper.mapTo(user); + + return ResponseEntity.ok() + .body(ResponseDto.builder() + .status(HttpStatus.OK) + .success(true) + .data(userProfileDto) + .build()); + } + + public UserProfile createUserProfile(User user){ + return UserProfile.builder() + .user(user).build(); + } + + @Override + public ResponseEntity UpdateUserProfile(int userId, UserProfileDto updateRequest) { + User user = userRepository.findById(updateRequest.getId()). + orElseThrow(() -> new UsernameNotFoundException("user Not Found")); + // check if this user try to update another user profile + if(userId!=user.getId()){ + throw new RuntimeException("this User can't update another user profile"); + } + return ResponseEntity.ok() + .body(ResponseDto.builder() + .status(HttpStatus.OK) + .success(true) + .data(doUpdate(updateRequest,user)) + .build()); + } + + private UserProfileDto doUpdate(UserProfileDto updateRequest, User user) { + //update fields that in user table + user.setFirstName(updateRequest.getFirstName()); + user.setLastName(updateRequest.getLastName()); + + //get userProfile and update it. + UserProfile userProfile=user.getUserProfile(); + userProfile.setDescription(updateRequest.getDescription()); + userProfile.setLocation(updateRequest.getLocation()); + userProfile.setTitle(userProfile.getTitle()); + userProfile.setHourlyRate(updateRequest.getHourlyRate()); + + // update user in database + user.setUserProfile(userProfile); + userRepository.save(user); + return updateRequest; + } + +} diff --git a/src/main/java/com/activecourses/upwork/service/user/UserProfileService.java b/src/main/java/com/activecourses/upwork/service/user/UserProfileService.java new file mode 100644 index 0000000..0d50830 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/service/user/UserProfileService.java @@ -0,0 +1,13 @@ +package com.activecourses.upwork.service.user; + +import com.activecourses.upwork.dto.user.UserProfileDto; +import com.activecourses.upwork.model.User; +import com.activecourses.upwork.model.UserProfile; +import org.springframework.http.ResponseEntity; + +public interface UserProfileService { + ResponseEntity getUserProfile(int id); + UserProfile createUserProfile(User user); + + ResponseEntity UpdateUserProfile(int userId, UserProfileDto updateRequest); +} diff --git a/src/main/java/com/activecourses/upwork/service/user/UserService.java b/src/main/java/com/activecourses/upwork/service/user/UserService.java index 432fca0..15cba7b 100644 --- a/src/main/java/com/activecourses/upwork/service/user/UserService.java +++ b/src/main/java/com/activecourses/upwork/service/user/UserService.java @@ -1,8 +1,11 @@ package com.activecourses.upwork.service.user; import com.activecourses.upwork.dto.user.UserResponseDto; +import com.activecourses.upwork.model.User; public interface UserService { UserResponseDto getAllUsers(int pageNo, int pageSize, String sortBy, String sortDir); + User findByEmail(String email); + } diff --git a/src/main/java/com/activecourses/upwork/service/user/userServiceImpl.java b/src/main/java/com/activecourses/upwork/service/user/userServiceImpl.java index 5bac67b..72afd00 100644 --- a/src/main/java/com/activecourses/upwork/service/user/userServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/user/userServiceImpl.java @@ -5,24 +5,24 @@ import com.activecourses.upwork.mapper.user.UserDtoMapper; import com.activecourses.upwork.model.User; import com.activecourses.upwork.repository.user.UserRepository; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; @Service -public class userServiceImpl implements UserService { +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { private final UserRepository userRepository; private final UserDtoMapper userDtoMapper; - public userServiceImpl(UserRepository userRepository, UserDtoMapper userDtoMapper) { - this.userRepository = userRepository; - this.userDtoMapper = userDtoMapper; - } + @Override public UserResponseDto getAllUsers(int pageNo, int pageSize, String sortBy, String sortDir) { @@ -54,4 +54,11 @@ public UserResponseDto getAllUsers(int pageNo, int pageSize, String sortBy, Stri userResponseDto.setLast(pagedResult.isLast()); return userResponseDto; } + @Override + public User findByEmail(String email) { + return userRepository.findByEmail(email). + orElseThrow(()->new UsernameNotFoundException("User Not Found")); + } + + } diff --git a/src/main/resources/db/migration/V5_Create_user_profile_to_all_inserted_users.sql b/src/main/resources/db/migration/V5_Create_user_profile_to_all_inserted_users.sql new file mode 100644 index 0000000..db51e7b --- /dev/null +++ b/src/main/resources/db/migration/V5_Create_user_profile_to_all_inserted_users.sql @@ -0,0 +1,5 @@ +INSERT INTO user_profiles (user_id) +SELECT u.id +FROM users u + LEFT JOIN user_profiles up ON u.id = up.user_id +WHERE up.user_id IS NULL; \ No newline at end of file