Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…orea into feat/#743
  • Loading branch information
chlwlstlf committed Nov 13, 2024
2 parents 969ccad + 19e996b commit 377bc85
Show file tree
Hide file tree
Showing 39 changed files with 725 additions and 197 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

public enum AlarmActionType {
REVIEW_COMPLETE,
REVIEW_URGE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public Set<Long> getRoomIds() {
return data.values()
.stream()
.flatMap(Collection::stream)
.filter(alarm -> alarm.getAlarmActionType() == AlarmActionType.REVIEW_COMPLETE)
//.filter(alarm -> alarm.getAlarmActionType() == AlarmActionType.REVIEW_COMPLETE)
.map(UserToUserAlarm::getInteractionId)
.collect(Collectors.toSet());
}
Expand All @@ -28,6 +28,6 @@ public List<UserToUserAlarm> getList() {
return data.values()
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
.toList();
}
}
4 changes: 4 additions & 0 deletions backend/src/main/java/corea/alarm/domain/UserToUserAlarm.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ public boolean isNotReceiver(Member member) {
public void read() {
isRead = true;
}

public boolean isUrgeAlarm() {
return alarmActionType == AlarmActionType.REVIEW_URGE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.RequiredArgsConstructor;

import java.util.EnumMap;
import java.util.List;
import java.util.stream.Collectors;

@Reader
Expand Down Expand Up @@ -36,4 +37,10 @@ public UserAlarmsByActionType findAllByReceiver(Member member) {
Collectors.toList()
)));
}

public boolean existUnReadUrgeAlarm(long revieweeId, long reviewerId, long roomId) {
List<UserToUserAlarm> alarm = userToUserAlarmRepository.findAllByActorIdAndReceiverIdAndInteractionId(revieweeId, reviewerId, roomId);
return alarm.stream()
.anyMatch(userToUserAlarm -> userToUserAlarm.isUrgeAlarm() && !userToUserAlarm.isRead());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
public interface UserToUserAlarmRepository extends JpaRepository<UserToUserAlarm, Long> {

List<UserToUserAlarm> findAllByReceiverId(long receiverId);

List<UserToUserAlarm> findAllByActorIdAndReceiverIdAndInteractionId(long actorId, long receiverId, long interactionId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
@Schema(description = "알림 체크 요청")
public record AlarmCheckRequest(
@Schema(description = "액션 아이디", example = "4")
long actionId,
long alarmId,

@Schema(description = "알림 타입", example = "USER")
String alarmType) {
Expand Down
16 changes: 15 additions & 1 deletion backend/src/main/java/corea/alarm/service/AlarmService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import corea.alarm.domain.UserToUserAlarmReader;
import corea.alarm.domain.UserToUserAlarmWriter;
import corea.alarm.dto.CreateUserToUserAlarmInput;
import corea.exception.CoreaException;
import corea.exception.ExceptionType;
import corea.member.domain.Member;
import corea.member.domain.MemberReader;
import corea.room.domain.Room;
Expand Down Expand Up @@ -63,8 +65,20 @@ public void checkAlarm(long userId, AlarmCheckRequest request) {
Member member = memberReader.findOne(userId);
AlarmType alarmType = AlarmType.from(request.alarmType());
if (alarmType == AlarmType.USER) {
UserToUserAlarm userToUserAlarm = userToUserAlarmReader.find(request.actionId());
UserToUserAlarm userToUserAlarm = userToUserAlarmReader.find(request.alarmId());
userToUserAlarmWriter.check(member, userToUserAlarm);
}
}

@Transactional
public void createUrgeAlarm(long revieweeId, long reviewerId, long roomId) {
boolean unReadUrgeAlarmExist = userToUserAlarmReader.existUnReadUrgeAlarm(revieweeId, reviewerId, roomId);
if (unReadUrgeAlarmExist) {
log.warn("리뷰 재촉 알림 생성을 실패했습니다. 리뷰어 ID={},리뷰이 ID={},방 ID={}",
reviewerId, revieweeId, roomId);
throw new CoreaException(ExceptionType.SAME_UNREAD_ALARM_EXIST);
}
CreateUserToUserAlarmInput input = new CreateUserToUserAlarmInput(AlarmActionType.REVIEW_URGE, revieweeId, reviewerId, roomId);
userToUserAlarmWriter.create(input.toEntity());
}
}
1 change: 1 addition & 0 deletions backend/src/main/java/corea/exception/ExceptionType.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum ExceptionType {
ALREADY_COMPLETED_REVIEW(HttpStatus.BAD_REQUEST, "이미 리뷰를 완료했습니다."),

NOT_RECEIVED_ALARM(HttpStatus.BAD_REQUEST,"본인이 받은 알람이 아닙니다."),
SAME_UNREAD_ALARM_EXIST(HttpStatus.BAD_REQUEST, "상대방에게 읽지 않은 같은 알람이 존재합니다."),

ALREADY_COMPLETED_FEEDBACK(HttpStatus.BAD_REQUEST, "이미 작성한 피드백이 존재합니다."),
INVALID_CALCULATION_FORMULA(HttpStatus.BAD_REQUEST, "잘못된 계산식입니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package corea.matching.infrastructure;

import corea.auth.infrastructure.GithubPersonalAccessTokenProvider;
import corea.matching.infrastructure.dto.PullRequestResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

import static org.springframework.http.MediaType.APPLICATION_JSON;

@Slf4j
@Component
@RequiredArgsConstructor
public class PrivateGithubPullRequestClient {

private final RestClient restClient;
private final GithubPersonalAccessTokenProvider githubPersonalAccessTokenProvider;

public PullRequestResponse getPullRequest(String repositoryLink, String username) {
String apiLink = convertApiLink(repositoryLink, username);
log.debug("요청 링크:{}", repositoryLink);
return restClient.get()
.uri(apiLink)
.header(HttpHeaders.AUTHORIZATION, githubPersonalAccessTokenProvider.getRandomPersonalAccessToken())
.accept(APPLICATION_JSON)
.exchange((clientRequest, clientResponse) -> {
if (clientResponse.getStatusCode().is4xxClientError()) {
return null;
}
PullRequestResponse[] data = clientResponse.bodyTo(PullRequestResponse[].class);
return data.length > 0 ? data[0] : null;
});
}

private String convertApiLink(String repositoryLink, String username) {
String[] parts = repositoryLink.substring(8)
.split("/");
String repoName = parts[2];
return String.format("https://api.github.com/repos/%s/%s-%s/pulls", username, repoName, username);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package corea.matching.service;

import corea.matching.domain.PullRequestInfo;
import corea.matching.infrastructure.PrivateGithubPullRequestClient;
import corea.matching.infrastructure.dto.PullRequestResponse;
import corea.member.domain.MemberReader;
import corea.participation.domain.ParticipationReader;
import corea.room.domain.Room;
import corea.room.domain.RoomReader;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
public class PrivatePullRequestProvider {

private final PrivateGithubPullRequestClient privateGithubPullRequestClient;
private final RoomReader roomReader;
private final MemberReader memberReader;
private final ParticipationReader participationReader;


public PullRequestInfo getEachRepository(long roomId) {
Room room = roomReader.find(roomId);
List<Long> memberIds = participationReader.findRevieweeIdsByRoomId(roomId);
List<String> members = memberReader.findUsernamesByIds(memberIds);

return new PullRequestInfo(members.stream()
.map(username -> privateGithubPullRequestClient.getPullRequest(room.getRepositoryLink(), username))
.filter(Objects::nonNull)
.collect(Collectors.toMap(PullRequestResponse::getUserId, Function.identity())));
}
}
8 changes: 8 additions & 0 deletions backend/src/main/java/corea/member/domain/MemberReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import corea.member.repository.ReviewerRepository;
import lombok.RequiredArgsConstructor;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand All @@ -29,6 +30,13 @@ public Map<Long, Member> findMembersMappedById(Iterable<Long> memberIds) {
.collect(Collectors.toMap(Member::getId, Function.identity()));
}

public List<String> findUsernamesByIds(Iterable<Long> memberIds) {
return memberRepository.findAllById(memberIds)
.stream()
.map(Member::getUsername)
.toList();
}

public boolean isReviewer(String githubUserId) {
return reviewerRepository.existsByGithubUserId(githubUserId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package corea.participation.domain;

import corea.member.domain.Member;
import corea.member.domain.MemberRole;
import corea.participation.repository.ParticipationRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class ParticipationReader {
Expand All @@ -16,4 +19,13 @@ public MemberRole findMemberRole(long roomId, long memberId) {
.map(Participation::getMemberRole)
.orElse(MemberRole.NONE);
}

public List<Long> findRevieweeIdsByRoomId(long roomId) {
return participationRepository.findAllByRoomId(roomId)
.stream()
.filter(Participation::isNotReviewer)
.map(Participation::getMember)
.map(Member::getId)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import corea.auth.annotation.LoginMember;
import corea.auth.domain.AuthInfo;
import corea.review.dto.ReviewRequest;
import corea.review.dto.UrgeRequest;
import corea.review.service.ReviewService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand All @@ -24,4 +25,11 @@ public ResponseEntity<Void> complete(@LoginMember AuthInfo authInfo, @RequestBod
return ResponseEntity.ok()
.build();
}

@PostMapping("/urge")
public ResponseEntity<Void> urge(@LoginMember AuthInfo authInfo, @RequestBody UrgeRequest request) {
reviewService.urgeReview(request.roomId(), request.reviewerId(), authInfo.getId());
return ResponseEntity.ok()
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import corea.exception.ExceptionType;
import corea.global.annotation.ApiErrorResponses;
import corea.review.dto.ReviewRequest;
import corea.review.dto.UrgeRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
Expand All @@ -18,7 +19,18 @@ public interface ReviewControllerSpecification {
"JWT 토큰에서 추출된 사용자 정보는 피드백 작성에 필요한 인증된 사용자 정보를 제공합니다. " +
"<br><br>**참고:** 이 API를 사용하기 위해서는 유효한 JWT 토큰이 필요하며, " +
"토큰이 없거나 유효하지 않은 경우 인증 오류가 발생합니다.")
@ApiErrorResponses(value = {ExceptionType.NOT_MATCHED_MEMBER,ExceptionType.ROOM_STATUS_IS_NOT_PROGRESS})
@ApiErrorResponses(value = {ExceptionType.NOT_MATCHED_MEMBER, ExceptionType.ROOM_STATUS_IS_NOT_PROGRESS})
ResponseEntity<Void> complete(AuthInfo authInfo,
ReviewRequest request);

@Operation(summary = "리뷰 재촉 알람을 보냅니다.",
description = "자신에게 배정된 리뷰어에게 리뷰 재촉 알람을 보낼 수 있습니다. <br>" +
"요청 시 `Authorization Header`에 `Bearer JWT token`을 포함시켜야 합니다. " +
"이 토큰을 기반으로 `AuthInfo` 객체가 생성되며 사용자의 정보가 자동으로 주입됩니다. <br>" +
"JWT 토큰에서 추출된 사용자 정보는 피드백 작성에 필요한 인증된 사용자 정보를 제공합니다. " +
"<br><br>**참고:** 이 API를 사용하기 위해서는 유효한 JWT 토큰이 필요하며, " +
"토큰이 없거나 유효하지 않은 경우 인증 오류가 발생합니다.")
@ApiErrorResponses(value = {ExceptionType.ALREADY_COMPLETED_REVIEW, ExceptionType.ROOM_STATUS_IS_NOT_PROGRESS, ExceptionType.SAME_UNREAD_ALARM_EXIST})
ResponseEntity<Void> urge(AuthInfo authInfo,
UrgeRequest request);
}
11 changes: 11 additions & 0 deletions backend/src/main/java/corea/review/dto/UrgeRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package corea.review.dto;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "리뷰 재촉 요청")
public record UrgeRequest(@Schema(description = "방 아이디", example = "1")
long roomId,

@Schema(description = "리뷰어 아이디", example = "2")
long reviewerId) {
}
14 changes: 14 additions & 0 deletions backend/src/main/java/corea/review/service/ReviewService.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,18 @@ private String getPrReviewLink(String prLink, String reviewerGithubId) {
.map(GithubPullRequestReview::html_url)
.orElseThrow(() -> new CoreaException(ExceptionType.NOT_COMPLETE_GITHUB_REVIEW));
}

@Transactional
public void urgeReview(long roomId, long reviewerId, long revieweeId) {
boolean isNotProgress = roomReader.isNotStatus(roomId, RoomStatus.PROGRESS);
if (isNotProgress) {
throw new CoreaException(ExceptionType.ROOM_STATUS_IS_NOT_PROGRESS);
}
MatchResult matchResult = matchResultReader.findOne(roomId, reviewerId, revieweeId);
if (matchResult.isReviewed()) {
throw new CoreaException(ExceptionType.ALREADY_COMPLETED_REVIEW);
}

alarmService.createUrgeAlarm(revieweeId, reviewerId, roomId);
}
}
34 changes: 34 additions & 0 deletions backend/src/main/java/corea/room/domain/RoomMatchInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package corea.room.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import static jakarta.persistence.GenerationType.IDENTITY;

@Entity
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class RoomMatchInfo {

@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;

private long roomId;

private boolean isPublic;

public RoomMatchInfo(final long roomId, final boolean isPublic) {
this(null, roomId, isPublic);
}

public boolean isPublic() {
return isPublic;
}
}
16 changes: 16 additions & 0 deletions backend/src/main/java/corea/room/domain/RoomMatchInfoWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package corea.room.domain;

import corea.global.annotation.Writer;
import corea.room.repository.RoomMatchInfoRepository;
import lombok.RequiredArgsConstructor;

@Writer
@RequiredArgsConstructor
public class RoomMatchInfoWriter {

private final RoomMatchInfoRepository roomMatchInfoRepository;

public RoomMatchInfo create(Room room, boolean isPrivate) {
return roomMatchInfoRepository.save(new RoomMatchInfo(room.getId(), isPrivate));
}
}
20 changes: 20 additions & 0 deletions backend/src/main/java/corea/room/domain/RoomMatchReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package corea.room.domain;

import corea.exception.CoreaException;
import corea.exception.ExceptionType;
import corea.global.annotation.Reader;
import corea.room.repository.RoomMatchInfoRepository;
import lombok.RequiredArgsConstructor;

@Reader
@RequiredArgsConstructor
public class RoomMatchReader {

private final RoomMatchInfoRepository roomMatchInfoRepository;

public boolean isPublicRoom(Room room) {
return roomMatchInfoRepository.findByRoomId(room.getId())
.map(RoomMatchInfo::isPublic)
.orElse(true);
}
}
Loading

0 comments on commit 377bc85

Please sign in to comment.