Skip to content

Commit

Permalink
Merge pull request #14 from Leets-Official/feat/#13/팔로우-언팔로우-관련-기능-구현…
Browse files Browse the repository at this point in the history
…-및-프로필-정보-조회

[feat] 팔로우 언팔로우 관련 기능 구현 및 프로필 정보 조회
  • Loading branch information
ehs208 authored Oct 30, 2024
2 parents e5226f2 + 8df1e9f commit c093e0a
Show file tree
Hide file tree
Showing 41 changed files with 608 additions and 322 deletions.
6 changes: 3 additions & 3 deletions src/main/java/com/leets/xcellentbe/XcellentBeApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
@EnableJpaAuditing
public class XcellentBeApplication {

public static void main(String[] args) {
SpringApplication.run(XcellentBeApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(XcellentBeApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.leets.xcellentbe.domain.follow.controller;

import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.leets.xcellentbe.domain.follow.dto.FollowRequestDto;
import com.leets.xcellentbe.domain.follow.dto.FollowInfoResponseDto;
import com.leets.xcellentbe.domain.follow.service.FollowService;
import com.leets.xcellentbe.global.response.GlobalResponseDto;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/profile")
@RequiredArgsConstructor
public class FollowController {
private final FollowService followService;

@PostMapping("/follow")
@Operation(summary = "팔로우", description = "다른 사용자를 팔로우합니다.")
public ResponseEntity<GlobalResponseDto<FollowRequestDto>> followUser(@RequestBody FollowRequestDto requestDto,
HttpServletRequest request) {
followService.followUser(requestDto, request);
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success());
}

@DeleteMapping("/follow")
@Operation(summary = "언팔로우", description = "다른 사용자를 언팔로우합니다.")
public ResponseEntity<GlobalResponseDto<FollowRequestDto>> unfollowerUser(@RequestBody FollowRequestDto requestDto,
HttpServletRequest request) {
followService.unfollowUser(requestDto, request);
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success());
}

@GetMapping("/following")
@Operation(summary = "팔로잉 조회", description = "사용자가 팔로우하는 사용자 목록을 조회합니다.")
public ResponseEntity<GlobalResponseDto<Page<FollowInfoResponseDto>>> getFollowingList(
@RequestParam(required = false, defaultValue = "1") int pageNo, @RequestParam String customId) {
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success(followService.getFollowingList(customId, pageNo - 1)));
}

@GetMapping("/follower")
@Operation(summary = "팔로워 조회", description = "사용자를 팔로우하는 사용자 목록을 조회합니다.")
public ResponseEntity<GlobalResponseDto<Page<FollowInfoResponseDto>>> getFollowerList(
@RequestParam(required = false, defaultValue = "1") int pageNo, @RequestParam String customId) {
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success(followService.getFollowerList(customId, pageNo - 1)));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.leets.xcellentbe.domain.follow.domain;

import com.leets.xcellentbe.domain.shared.BaseTimeEntity;
import com.leets.xcellentbe.domain.user.domain.User;

import jakarta.persistence.Entity;
Expand All @@ -11,13 +12,14 @@
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Follow {
public class Follow extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -32,4 +34,19 @@ public class Follow {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "follower_id")
private User follower;

@Builder
private Follow(User following, User follower) {
this.following = following;
this.follower = follower;
}

public static Follow create(User follower, User following) {
return Follow.builder()
.follower(follower)
.following(following)
.build();
}
}


Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package com.leets.xcellentbe.domain.follow.domain.repository;

import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import com.leets.xcellentbe.domain.follow.domain.Follow;
import com.leets.xcellentbe.domain.user.domain.User;

public interface FollowRepository extends JpaRepository<Follow, Long> {
Optional<Follow> findByFollowerAndFollowing(User user, User targetUser);

Page<Follow> findByFollower(User user, Pageable pageable);

Page<Follow> findByFollowing(User user, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.leets.xcellentbe.domain.follow.dto;

import com.leets.xcellentbe.domain.follow.domain.Follow;
import com.leets.xcellentbe.domain.user.domain.User;

import lombok.Builder;
import lombok.Getter;

@Getter
public class FollowInfoResponseDto {
private String customId;
private String userName;

@Builder
public FollowInfoResponseDto(String customId, String userName) {
this.customId = customId;
this.userName = userName;
}

public static FollowInfoResponseDto from(Follow follow) {
User user = follow.getFollowing();
return FollowInfoResponseDto.builder()
.customId(user.getCustomId())
.userName(user.getUserName())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.leets.xcellentbe.domain.follow.dto;

import lombok.Getter;

@Getter
public class FollowRequestDto {
private String customId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.leets.xcellentbe.domain.follow.exception;

import com.leets.xcellentbe.global.error.ErrorCode;
import com.leets.xcellentbe.global.error.exception.CommonException;

public class FollowOperationError extends CommonException {
public FollowOperationError() {
super(ErrorCode.FOLLOW_OPERATION_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.leets.xcellentbe.domain.follow.service;

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.stereotype.Service;

import com.leets.xcellentbe.domain.follow.domain.Follow;
import com.leets.xcellentbe.domain.follow.domain.repository.FollowRepository;
import com.leets.xcellentbe.domain.follow.dto.FollowInfoResponseDto;
import com.leets.xcellentbe.domain.follow.dto.FollowRequestDto;
import com.leets.xcellentbe.domain.follow.exception.FollowOperationError;
import com.leets.xcellentbe.domain.user.domain.User;
import com.leets.xcellentbe.domain.user.domain.repository.UserRepository;
import com.leets.xcellentbe.domain.user.exception.UserNotFoundException;
import com.leets.xcellentbe.global.auth.jwt.JwtService;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;

@Service
@Transactional
@AllArgsConstructor
public class FollowService {

private static final int PAGE_SIZE = 10;
private final UserRepository userRepository;
private final JwtService jwtService;
private final FollowRepository followRepository;

// 팔로우
public void followUser(FollowRequestDto requestDto, HttpServletRequest request) {
User user = getUser(request);
User targetUser = getTargetUser(requestDto);

if (isFollowing(user, targetUser)) {
throw new FollowOperationError();
}

Follow follow = Follow.create(user, targetUser);
followRepository.save(follow);
}

// 이미 팔로우 중인지 확인
private boolean isFollowing(User user, User targetUser) {
return followRepository.findByFollowerAndFollowing(user, targetUser).isPresent();
}

// 언팔로우
public void unfollowUser(FollowRequestDto requestDto, HttpServletRequest request) {
User user = getUser(request);
User targetUser = getTargetUser(requestDto);

Follow follow = getFollowRelation(user, targetUser);

followRepository.delete(follow);
}

// 팔로우 관계 조회
private Follow getFollowRelation(User user, User targetUser) {
Follow follow = followRepository.findByFollowerAndFollowing(user, targetUser)
.orElseThrow(FollowOperationError::new);
return follow;
}

// 팔로우(언팔로우) 대상 유저 조회
private User getTargetUser(FollowRequestDto requestDto) {
User targetUser = userRepository.findByCustomId(requestDto.getCustomId())
.orElseThrow(UserNotFoundException::new);
return targetUser;
}

// 팔로잉 목록 조회
public Page<FollowInfoResponseDto> getFollowingList(String customId, int pageNo) {
User user = findUserByCustomId(customId);
Pageable pageable = createPageable(pageNo);

return followRepository.findByFollower(user, pageable)
.map(FollowInfoResponseDto::from);
}

// 팔로워 목록 조회
public Page<FollowInfoResponseDto> getFollowerList(String customId, int pageNo) {
User user = findUserByCustomId(customId);
Pageable pageable = createPageable(pageNo);

return followRepository.findByFollowing(user, pageable)
.map(FollowInfoResponseDto::from);
}

// 커스텀아이디로 유저 검색
private User findUserByCustomId(String customId) {
return userRepository.findByCustomId(customId)
.orElseThrow(UserNotFoundException::new);
}

// 페이지네이션 객체 생성
private Pageable createPageable(int pageNo) {
return PageRequest.of(pageNo, PAGE_SIZE, Sort.by(Sort.Direction.DESC, "following"));
}

//JWT 토큰 해독하여 사용자 정보 반환 메소드
private User getUser(HttpServletRequest request) {
User user = jwtService.extractAccessToken(request)
.filter(jwtService::isTokenValid)
.flatMap(jwtService::extractEmail)
.flatMap(userRepository::findByEmail)
.orElseThrow(UserNotFoundException::new);

return user;
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
package com.leets.xcellentbe.domain.user.controller;

import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.leets.xcellentbe.domain.user.domain.User;
import com.leets.xcellentbe.domain.user.dto.UserLoginRequestDto;
import com.leets.xcellentbe.domain.user.dto.UserProfileRequestDto;
import com.leets.xcellentbe.domain.user.dto.UserProfileResponseDto;
import com.leets.xcellentbe.domain.user.dto.UserSignUpRequestDto;
import com.leets.xcellentbe.domain.user.service.S3UploadService;
import com.leets.xcellentbe.domain.user.service.UserService;
import com.leets.xcellentbe.global.auth.email.EmailRequestDto;
import com.leets.xcellentbe.global.response.GlobalResponseDto;

import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -35,32 +25,43 @@
public class UserController {
private final UserService userService;

@GetMapping("/info")
@Operation(summary = "프로필 조회", description = "사용자의 프로필 내용을 조회합니다.")
@GetMapping("/myinfo")
@Operation(summary = "본인 프로필 조회", description = "본인의 프로필 내용을 조회합니다.")
public ResponseEntity<GlobalResponseDto<UserProfileResponseDto>> getProfile(HttpServletRequest request) {
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success(userService.getProfile(request)));
}

@PatchMapping("/info")
@Operation(summary = "프로필 수정", description = "사용자의 프로필을 수정합니다.")
public ResponseEntity<GlobalResponseDto<String>> updateProfile(@RequestBody UserProfileRequestDto userProfileRequestDto, HttpServletRequest request) {
userService.updateProfile(request,userProfileRequestDto);
@PatchMapping("/myinfo")
@Operation(summary = "본인 프로필 수정", description = "본인의 프로필을 수정합니다.")
public ResponseEntity<GlobalResponseDto<String>> updateProfile(
@RequestBody UserProfileRequestDto userProfileRequestDto, HttpServletRequest request) {
userService.updateProfile(request, userProfileRequestDto);
return ResponseEntity.status(HttpStatus.OK).body(GlobalResponseDto.success());
}

@GetMapping("/info")
@Operation(summary = "프로필 조회", description = "특정 사용자의 프로필 내용을 조회합니다.")
public ResponseEntity<GlobalResponseDto<UserProfileResponseDto>> getProfileWithoutToken(
@RequestParam String customId) {
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success(userService.getProfileWithoutToken(customId)));
}

@PatchMapping("/profile-image")
@Operation(summary = "프로필 이미지 수정", description = "사용자의 프로필 이미지를 수정합니다.")
public ResponseEntity<GlobalResponseDto<String>> updateProfileImage(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success(userService.updateProfileImage(file, request)));
public ResponseEntity<GlobalResponseDto<String>> updateProfileImage(@RequestParam("file") MultipartFile file,
HttpServletRequest request) {
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success(userService.updateProfileImage(file, request)));

}

@PatchMapping("/background-image")
@Operation(summary = "배경 이미지 수정", description = "사용자의 배경 이미지를 수정합니다.")
public ResponseEntity<GlobalResponseDto<String>> updateBackgroundImage (@RequestParam("file") MultipartFile file, HttpServletRequest request){
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success(userService.updateBackgroundProfileImage(file, request)));
}
public ResponseEntity<GlobalResponseDto<String>> updateBackgroundImage(@RequestParam("file") MultipartFile file,
HttpServletRequest request) {
return ResponseEntity.status(HttpStatus.OK)
.body(GlobalResponseDto.success(userService.updateBackgroundProfileImage(file, request)));
}
}
Loading

0 comments on commit c093e0a

Please sign in to comment.