Skip to content

Commit

Permalink
[merge]: friend API κ΅¬ν˜„ (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
kanguk01 authored Oct 25, 2024
2 parents 492f980 + 032a7b8 commit e54b8b4
Show file tree
Hide file tree
Showing 19 changed files with 1,045 additions and 8 deletions.
11 changes: 11 additions & 0 deletions src/main/java/com/splanet/splanet/core/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ public enum ErrorCode {
INVITATION_ALREADY_PROCESSED("μ΄ˆλŒ€κ°€ 이미 μ²˜λ¦¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST),
USER_ALREADY_IN_TEAM("ν•΄λ‹Ή μœ μ €λŠ” 이미 νŒ€μ— 속해 μžˆμŠ΅λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST),


// friend
FRIEND_NOT_FOUND("μΉœκ΅¬κ°€ μ•„λ‹™λ‹ˆλ‹€.",HttpStatus.NOT_FOUND),
FRIEND_REQUEST_NOT_FOUND("ν•΄λ‹Ή 친ꡬ μš”μ²­μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.", HttpStatus.NOT_FOUND),
FRIEND_ALREADY_EXISTS("이미 친ꡬ λͺ©λ‘μ— μžˆμŠ΅λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST),
FRIEND_REQUEST_ALREADY_SENT("이미 μš”μ²­μ„ λ³΄λƒˆμŠ΅λ‹ˆλ‹€.",HttpStatus.BAD_REQUEST),
FRIEND_REQUEST_NOT_FOUND_IN_RECEIVED_LIST("λ‚΄κ°€ 받은 μš”μ²­μ΄ μ•„λ‹™λ‹ˆλ‹€.", HttpStatus.NOT_FOUND),
FRIEND_REQUEST_NOT_RECEIVER("본인이 보낸 μš”μ²­μ€ μˆ˜λ½ν•˜κ±°λ‚˜ κ±°μ ˆν•  수 μ—†μŠ΅λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST),
FRIEND_REQUEST_ALREADY_ACCEPTED_OR_REJECTED("이미 μˆ˜λ½ν•˜κ±°λ‚˜ κ±°μ ˆν•œ μ‚¬μš©μž μž…λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST),
SELF_FRIEND_REQUEST_NOT_ALLOWED("λ³ΈμΈμ—κ²Œ μΉœκ΅¬μš”μ²­μ„ 보낼 수 μ—†μŠ΅λ‹ˆλ‹€.", HttpStatus.BAD_REQUEST),

// redis
REDIS_SCAN_FAILED("Redis ν‚€ μŠ€μΊ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", HttpStatus.SERVICE_UNAVAILABLE);

Expand Down
50 changes: 50 additions & 0 deletions src/main/java/com/splanet/splanet/friend/controller/FriendApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.splanet.splanet.friend.controller;

import com.splanet.splanet.friend.dto.FriendResponse;
import com.splanet.splanet.plan.dto.PlanResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;
import java.util.Map;

@RequestMapping("/api/friends")
@Tag(name = "Friend", description = "친ꡬ κ΄€λ ¨ API")
public interface FriendApi {

@GetMapping
@Operation(summary = "친ꡬ λͺ©λ‘ 쑰회", description = "μ‚¬μš©μžμ˜ 친ꡬ λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "친ꡬ λͺ©λ‘μ΄ μ„±κ³΅μ μœΌλ‘œ μ‘°νšŒλ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
@ApiResponse(responseCode = "401", description = "μΈμ¦λ˜μ§€ μ•Šμ€ μ‚¬μš©μžμž…λ‹ˆλ‹€.")
})
ResponseEntity<List<FriendResponse>> getFriends(
@Parameter(description = "JWT 인증으둜 μ „λ‹¬λœ μ‚¬μš©μž ID", required = true) @AuthenticationPrincipal Long userId);

@GetMapping("/{friendId}/plans")
@Operation(summary = "친ꡬ ν”Œλžœ 쑰회", description = "친ꡬ의 곡개된 ν”Œλžœ λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "친ꡬ의 곡개된 κ³„νšμ΄ μ„±κ³΅μ μœΌλ‘œ μ‘°νšŒλ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
@ApiResponse(responseCode = "400", description = "잘λͺ»λœ μš”μ²­μž…λ‹ˆλ‹€ (μœ νš¨ν•˜μ§€ μ•Šμ€ 친ꡬ ID)."),
@ApiResponse(responseCode = "401", description = "μΈμ¦λ˜μ§€ μ•Šμ€ μ‚¬μš©μžμž…λ‹ˆλ‹€."),
@ApiResponse(responseCode = "404", description = "친ꡬλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")
})
ResponseEntity<List<PlanResponseDto>> getFriendPlan(
@Parameter(description = "μ‘°νšŒν•  친ꡬ ID", required = true) @PathVariable("friendId") Long friendId,
@Parameter(description = "JWT 인증으둜 μ „λ‹¬λœ μ‚¬μš©μž ID", required = true) @AuthenticationPrincipal Long userId);

@DeleteMapping("/{friendId}")
@Operation(summary = "친ꡬ μ‚­μ œν•˜κΈ°", description = "친ꡬ λͺ©λ‘μ—μ„œ μ‚­μ œν•©λ‹ˆλ‹€.")
ResponseEntity<Map<String, String>> unfriend(
@Parameter(description = "μ‚­μ œν•  친ꡬ ID", required = true) @PathVariable("friendId") Long friendId,
@Parameter(description = "JWT 인증으둜 μ „λ‹¬λœ μ‚¬μš©μž ID", required = true) @AuthenticationPrincipal Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.splanet.splanet.friend.controller;

import com.splanet.splanet.friend.dto.FriendResponse;
import com.splanet.splanet.friend.service.FriendService;
import com.splanet.splanet.plan.dto.PlanResponseDto;
import com.splanet.splanet.plan.repository.PlanRepository;
import com.splanet.splanet.user.repository.UserRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class FriendController implements FriendApi {

private final FriendService friendService;

public FriendController(FriendService friendService) {
this.friendService = friendService;;
}

@Override
public ResponseEntity<List<FriendResponse>> getFriends(Long userId) {
List<FriendResponse> friends = friendService.getFriends(userId);
return ResponseEntity.ok(friends);
}

@Override
public ResponseEntity<List<PlanResponseDto>> getFriendPlan(
@PathVariable Long friendId,
@AuthenticationPrincipal Long userId) {
return friendService.getFriendPlan(friendId, userId);
}

@Override
public ResponseEntity<Map<String, String>> unfriend(
@PathVariable Long friendId,
@AuthenticationPrincipal Long userId) {
ResponseEntity<Map<String, String>> responseEntity = friendService.unfriend(friendId, userId);
return responseEntity;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.splanet.splanet.friend.dto;

public record FriendResponse(Long userId, String nickname, String profileImage) {
}
11 changes: 7 additions & 4 deletions src/main/java/com/splanet/splanet/friend/entity/Friend.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.splanet.splanet.friend.entity;

import com.splanet.splanet.core.BaseEntity;
import com.splanet.splanet.user.entity.User;
import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.SuperBuilder;
Expand All @@ -13,9 +14,11 @@
@Entity
public class Friend extends BaseEntity {

@Column(name = "user_id", nullable = false)
private Long userId;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;

@Column(name = "friend_id", nullable = false)
private Long friendId;
@ManyToOne
@JoinColumn(name = "friend_id", nullable = false)
private User friend;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@

import com.splanet.splanet.friend.entity.Friend;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository
public interface FriendRepository extends JpaRepository<Friend, Long> {
List<Friend> findByUserId(Long userId);
boolean existsByUserIdAndFriendId(Long userId, Long friendId);

@Modifying
@Query("DELETE FROM Friend f WHERE f.user.id = :requesterId AND f.friend.id = :receiverId")
void deleteByRequesterIdAndReceiverId(@Param("requesterId") Long requesterId, @Param("receiverId") Long receiverId);
}
103 changes: 103 additions & 0 deletions src/main/java/com/splanet/splanet/friend/service/FriendService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.splanet.splanet.friend.service;

import com.splanet.splanet.core.exception.BusinessException;
import com.splanet.splanet.core.exception.ErrorCode;
import com.splanet.splanet.friend.dto.FriendResponse;
import com.splanet.splanet.friend.entity.Friend;
import com.splanet.splanet.friend.repository.FriendRepository;
import com.splanet.splanet.friendRequest.entity.FriendRequest;
import com.splanet.splanet.friendRequest.repository.FriendRequestRepository;
import com.splanet.splanet.plan.dto.PlanResponseDto;
import com.splanet.splanet.plan.entity.Plan;
import com.splanet.splanet.plan.repository.PlanRepository;
import com.splanet.splanet.user.entity.User;
import com.splanet.splanet.user.repository.UserRepository;
import jakarta.transaction.Transactional;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class FriendService {

private final FriendRepository friendRepository;
private final PlanRepository planRepository;
private final FriendRequestRepository friendRequestRepository;

public FriendService(FriendRepository friendRepository, PlanRepository planRepository, FriendRequestRepository friendRequestRepository) {
this.friendRepository = friendRepository;
this.planRepository = planRepository;
this.friendRequestRepository = friendRequestRepository;
}

// 친ꡬ λͺ©λ‘ 쑰회
public List<FriendResponse> getFriends(Long userId) {
List<Friend> friends = friendRepository.findByUserId(userId);

// Friend μ—”ν‹°ν‹°λ₯Ό FriendResponse DTO둜 λ³€ν™˜
return friends.stream()
.map(friend -> {
User friendUser = friend.getFriend();
return new FriendResponse(
friendUser.getId(),
friendUser.getNickname(),
friendUser.getProfileImage()
);
})
.collect(Collectors.toList());
}

// 친ꡬ의 곡개 ν”Œλžœ 쑰회
public ResponseEntity<List<PlanResponseDto>> getFriendPlan(Long friendId, Long userId) {
// 친ꡬ λͺ©λ‘μ— friendIdκ°€ μžˆλŠ”μ§€ 확인
boolean isFriend = friendRepository.existsByUserIdAndFriendId(userId, friendId);

if (!isFriend) {
throw new BusinessException(ErrorCode.FRIEND_NOT_FOUND);
}

List<Plan> publicPlans = planRepository.findAllByUserIdAndAccessibility(friendId, true);

// 곡개된 ν”Œλžœμ΄ 없을 경우, 빈 λͺ©λ‘ λ°˜ν™˜
if (publicPlans.isEmpty()) {
return ResponseEntity.ok(Collections.emptyList());
}

List<PlanResponseDto> planResponseDtos = publicPlans.stream()
.map(plan -> PlanResponseDto.builder()
.id(plan.getId())
.title(plan.getTitle())
.description(plan.getDescription())
.startDate(plan.getStartDate())
.endDate(plan.getEndDate())
.accessibility(plan.getAccessibility())
.isCompleted(plan.getIsCompleted())
.createdAt(plan.getCreatedAt())
.updatedAt(plan.getUpdatedAt())
.build())
.collect(Collectors.toList());

return ResponseEntity.ok(planResponseDtos);
}

// 친ꡬ μ‚­μ œ(μ·¨μ†Œ)ν•˜κΈ°
@Transactional
public ResponseEntity<Map<String, String>> unfriend(Long friendId, Long userId) {
if (!friendRepository.existsByUserIdAndFriendId(userId, friendId)) {
throw new BusinessException(ErrorCode.FRIEND_NOT_FOUND);
}

friendRepository.deleteByRequesterIdAndReceiverId(userId, friendId);

List<FriendRequest> pendingRequests = friendRequestRepository.findPendingRequestsByReceiverId(userId, friendId, FriendRequest.Status.PENDING);
for (FriendRequest request : pendingRequests) {
friendRequestRepository.delete(request);
}

return ResponseEntity.ok(Map.of("message", "친ꡬ λ§ΊκΈ° μ·¨μ†Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€!"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.splanet.splanet.friendRequest.controller;

import com.splanet.splanet.friendRequest.dto.FriendRequestCreateRequest;
import com.splanet.splanet.friendRequest.dto.ReceivedFriendRequestResponse;
import com.splanet.splanet.friendRequest.dto.SentFriendRequestResponse;
import com.splanet.splanet.friendRequest.dto.SuccessResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping("/api/friends/requests")
@Tag(name = "FriendRequest", description = "친ꡬ μš”μ²­ κ΄€λ ¨ API")
public interface FriendRequestApi {

@PostMapping
@Operation(summary = "친ꡬ μš”μ²­", description = "νŠΉμ • μ‚¬μš©μžμ—κ²Œ 친ꡬ μš”μ²­μ„ λ³΄λƒ…λ‹ˆλ‹€.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "친ꡬ μš”μ²­μ΄ μ„±κ³΅μ μœΌλ‘œ μ „μ†‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
@ApiResponse(responseCode = "400", description = "잘λͺ»λœ μš”μ²­μž…λ‹ˆλ‹€ (μœ νš¨ν•˜μ§€ μ•Šμ€ μœ μ € ID)."),
@ApiResponse(responseCode = "401", description = "μΈμ¦λ˜μ§€ μ•Šμ€ μ‚¬μš©μžμž…λ‹ˆλ‹€.")
})
ResponseEntity<SuccessResponse> sendFriendRequest(
@Parameter(description = "친ꡬ μš”μ²­μ„ 보낼 μ‚¬μš©μž ID", required = true) @AuthenticationPrincipal Long userId,
@RequestBody FriendRequestCreateRequest request);

@PostMapping("/{requestId}/accept")
@Operation(summary = "친ꡬ μš”μ²­ 수락", description = "친ꡬ μš”μ²­ 수락")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "친ꡬ μš”μ²­ μ„±κ³΅μ μœΌλ‘œ μˆ˜λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
@ApiResponse(responseCode = "400", description = "잘λͺ»λœ μš”μ²­μž…λ‹ˆλ‹€ (μœ νš¨ν•˜μ§€ μ•Šμ€ μœ μ € ID)."),
@ApiResponse(responseCode = "401", description = "μΈμ¦λ˜μ§€ μ•Šμ€ μ‚¬μš©μžμž…λ‹ˆλ‹€."),
@ApiResponse(responseCode = "404", description = "친ꡬ μš”μ²­μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")
})
ResponseEntity<ReceivedFriendRequestResponse> acceptFriendRequest(@AuthenticationPrincipal Long userId,
@Parameter(description = "친ꡬ μš”μ²­ ID", required = true) @PathVariable Long requestId);

@PostMapping("/{requestId}/reject")
@Operation(summary = "친ꡬ μš”μ²­ 거절", description = "친ꡬ μš”μ²­ 거절")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "친ꡬ μš”μ²­μ΄ μ„±κ³΅μ μœΌλ‘œ κ±°μ ˆλ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
@ApiResponse(responseCode = "401", description = "μΈμ¦λ˜μ§€ μ•Šμ€ μ‚¬μš©μžμž…λ‹ˆλ‹€."),
@ApiResponse(responseCode = "404", description = "친ꡬ μš”μ²­μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),
@ApiResponse(responseCode = "404", description = "친ꡬ μš”μ²­μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")
})
ResponseEntity<ReceivedFriendRequestResponse> rejectFriendRequest(
@Parameter(description = "친ꡬ μš”μ²­ ID", required = true) @PathVariable Long requestId, @AuthenticationPrincipal Long userId);

@GetMapping("/received")
@Operation(summary = "친ꡬ μš”μ²­ λͺ©λ‘ 쑰회 (받은 μš”μ²­)", description = "μ‚¬μš©μžκ°€ 받은 친ꡬ μš”μ²­ λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "받은 친ꡬ μš”μ²­ λͺ©λ‘μ΄ μ„±κ³΅μ μœΌλ‘œ μ‘°νšŒλ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
@ApiResponse(responseCode = "401", description = "μΈμ¦λ˜μ§€ μ•Šμ€ μ‚¬μš©μžμž…λ‹ˆλ‹€.")
})
ResponseEntity<List<ReceivedFriendRequestResponse>> getReceivedRequests(
@Parameter(description = "JWT 인증으둜 μ „λ‹¬λœ μ‚¬μš©μž ID", required = true) @AuthenticationPrincipal Long userId);

@GetMapping("/sent")
@Operation(summary = "친ꡬ μš”μ²­ λͺ©λ‘ 쑰회 (보낸 μš”μ²­)", description = "μ‚¬μš©μžκ°€ 보낸 친ꡬ μš”μ²­ λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "보낸 친ꡬ μš”μ²­ λͺ©λ‘μ΄ μ„±κ³΅μ μœΌλ‘œ μ‘°νšŒλ˜μ—ˆμŠ΅λ‹ˆλ‹€."),
@ApiResponse(responseCode = "401", description = "μΈμ¦λ˜μ§€ μ•Šμ€ μ‚¬μš©μžμž…λ‹ˆλ‹€.")
})
ResponseEntity<List<SentFriendRequestResponse>> getSentRequests(
@Parameter(description = "JWT 인증으둜 μ „λ‹¬λœ μ‚¬μš©μž ID", required = true) @AuthenticationPrincipal Long userId);
}
Loading

0 comments on commit e54b8b4

Please sign in to comment.