Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
pp449 committed Nov 14, 2024
2 parents 1c0de6d + 818f622 commit 271fb1d
Show file tree
Hide file tree
Showing 165 changed files with 3,741 additions and 663 deletions.
83 changes: 54 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,61 +1,86 @@
# Code Review Area

<br>
코드 리뷰 파트너 자동 매칭 플랫폼 **CoReA**로 완성하는 성장의 퍼즐 <br>
**코드, 리뷰, 그리고 당신**

혼자 공부하다 보면 막막할 때가 많습니다.

<br>
<br>

> '내가 지금 제대로 공부하고 있는 건가?' <br>
> '남들은 내 코드를 어떻게 생각하고 있을까?' <br>
> '좋은 코드에 대한 평가 기준은 없을까?'
>
<br>

저희도 그랬습니다. *옛날에는요.*
그렇다면 `나와 함께 성장할 동료 개발자`들은 어디서 구할 수 있을까요?

- 학교? 🏫
- 부트캠프? 🏕️
- 커뮤니티? 🤼‍♀️

<br>

## 함께 일하는 법
**CoReA**에서는 별도 구인 과정 없이도 자동 매칭된 동료 개발자와 지속적인 피드백을 통해 **함께 성장**할 수 있어요. <br>
**많은 사람들과 코드 리뷰**를 주고 받으며 다양한 관점을 발견해보세요!

<br>
<br>

어떤 사람을 우리는 좋은 개발자라고 말할까요? <br>
# 프로젝트 설명 및 사용법

다양한 연차의 개발자를 대상으로 진행된 한 인터뷰에서는 많은 사람들이 함께하고 싶은 동료로 <br>
`함께 성장하기 위해 노력하는 사람`, `근거와 함께 의견을 설득하는 사람` 등을 꼽았어요.
## 1. 미션 참여

<br>
코레아는 같은 미션을 각자 해결하고, 다른 사람들과 공유하고 코드리뷰를 통해 의견을 나누는 환경을 제공하는 곳이에요.
원하는 미션을 선택하고 방 세부 정보를 확인한 후 참여하세요.

그리고 그건 기업에서도 동일합니다.<br>
<img width="2055" alt="dd1" src="https://github.com/user-attachments/assets/442a18f0-bb22-470c-a863-b29584969e6c">

[기사](https://www.samsungsds.com/kr/insights/global_code_review.html)에서는 다음과 같이 말하고 있어요.
## 2. 리뷰어, 리뷰이 매칭

<br>
모집 마감 시간이 되면 방에 기재된 깃허브주소에 PR을 작성한 사람들끼리 리뷰어와 리뷰이가 자동으로 매칭돼요.

>100% 코드 리뷰, 모든 코드를 리뷰하는 **Google**<br>
개발자 업무는 개발 50%, 리뷰 50%인 **Microsoft**<br>
“리뷰는 당연하다”, **네카라쿠배**
<img width="1787" alt="dd2" src="https://github.com/user-attachments/assets/3e581d2b-2ff0-4cb4-8d2f-b896f6ec97b0">

<br>
## 3. 코드리뷰

`코드 리뷰`, 그리고 `함께`.<br>
매칭된 리뷰이의 PR 링크에서 깃허브 코드리뷰를 진행해요.

다 좋아요.
<img width="1757" alt="dd3" src="https://github.com/user-attachments/assets/746f74b4-748c-4445-afea-7daf139d06ca">

하지만 가장 중요한, `내가 함께 코드 리뷰를 할 동료 개발자`들은 어디서 구할 수 있을까요?
## 4. 피드백 작성

- 학교? 🏫
- 부트캠프? 🏕️
- 커뮤니티? 🤼‍♀️
두가지 방면에서 피드백을 남길 수 있어요.

<img width="1901" alt="dd4" src="https://github.com/user-attachments/assets/5f832e88-f3dc-45e9-a451-4866c6680ffc">

### 1. 리뷰어가 리뷰이에게 남기는 개발 피드백

코드리뷰를 마치고 코드리뷰 완료 버튼을 클릭하면 피드백을 작성할 수 있어요.
리뷰어의 코드리뷰에 대한 피드백을 남겨주세요.

### 2. 리뷰이가 리뷰어에게 남기는 소셜 피드백

리뷰어가 코드리뷰를 완료하면 피드백 작성 버튼이 활성화돼요.
버튼을 클릭하여 상대방의 리뷰에 대한 피드백을 남겨주세요.

## 5. 피드백 확인

나쁘지 않습니다.
피드백 모아보기에서 주고받은 모든 피드백을 확인할 수 있어요.

하지만 매번 나의 코드를 리뷰해줄 개발자를 일일이 구인하는 건 어려운 일이에요.<br>
리뷰해주겠다는 약속을 지키지 않는 사람들도 많고요.
<img width="2009" alt="dd5" src="https://github.com/user-attachments/assets/8c6af2e0-73aa-493d-9449-1b93ce679c4c">

<br><br>

# 기술 스택 소개

![dd](https://github.com/user-attachments/assets/a2411aee-bca9-4dc4-9332-87460e3e4f0f)

<br>
<br>

나에게 도움을 줄 수 있는 개발자이자 리뷰어를 `코레아`에서 찾아보는 건 어떠세요?<br>
또한 개발자의 코드와 리뷰어의 피드백을 통해 폭발적인 성장을 이루어보아요!
# 팀원 소개

코드에 대해 다양한 사람들과 의견을 얘기하며 내가 생각하지 못한 부분을 발견해보세요!<br>
그리고 리뷰어에게 피드백을 받아서 성장을 이뤄나가요! 🙂🙂
| <img src="https://github.com/user-attachments/assets/b29d41fa-2f5e-40ac-b5cd-531f0edf0aaa" width="100px"/> | <img src="https://github.com/user-attachments/assets/ce1dabf2-240c-48a9-b6db-0fa22811444b" width="100px"/> | <img src="https://github.com/user-attachments/assets/882edb5c-6cb0-4745-b22f-c292847a4e89" width="100px"/> | <img src="https://github.com/user-attachments/assets/0ddf7b9a-6914-4877-bdd7-924fb485285d" width="100px"/> | <img src="https://github.com/user-attachments/assets/3eda6b53-d7ca-48c9-b182-11d2bdcbacd4" width="100px"/> | <img src="https://github.com/user-attachments/assets/b1a303da-739b-4541-bc0e-7c444310521e" width="100px"/> | <img src="https://github.com/user-attachments/assets/2062bcff-d28f-454f-a7cd-e0f8b9aba823" width="100px"/> |
| :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------: |
| [<img src="https://cdn-icons-png.flaticon.com/512/2111/2111432.png" width="15px" height="15px" /> FE\_텐텐](https://github.com/chlwlstlf) | [<img src="https://cdn-icons-png.flaticon.com/512/2111/2111432.png" width="15px" height="15px" /> FE\_다르](https://github.com/pp449) | [<img src="https://cdn-icons-png.flaticon.com/512/2111/2111432.png" width="15px" height="15px" /> FE\_초코](https://github.com/00kang) | [<img src="https://cdn-icons-png.flaticon.com/512/2111/2111432.png" width="15px" height="15px" /> BE\_애쉬](https://github.com/ashsty) | [<img src="https://cdn-icons-png.flaticon.com/512/2111/2111432.png" width="15px" height="15px" /> BE\_무빈](https://github.com/hjk0761) | [<img src="https://cdn-icons-png.flaticon.com/512/2111/2111432.png" width="15px" height="15px" /> BE\_뽀로로](https://github.com/jcoding-play) | [<img src="https://cdn-icons-png.flaticon.com/512/2111/2111432.png" width="15px" height="15px" /> BE\_조이썬](https://github.com/youngsu5582) |
40 changes: 40 additions & 0 deletions backend/src/main/java/corea/alarm/controller/AlarmController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package corea.alarm.controller;

import corea.alarm.dto.AlarmCheckRequest;
import corea.alarm.dto.AlarmCountResponse;
import corea.alarm.dto.AlarmResponses;
import corea.alarm.service.AlarmService;
import corea.auth.annotation.LoginMember;
import corea.auth.domain.AuthInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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.RequestBody;

@Controller
@RequiredArgsConstructor
public class AlarmController implements AlarmControllerSpecification {

private final AlarmService alarmService;

@GetMapping("/alarm/count")
public ResponseEntity<AlarmCountResponse> getAlarmCount(@LoginMember AuthInfo authInfo) {
AlarmCountResponse response = alarmService.getUnReadAlarmCount(authInfo.getId());
return ResponseEntity.ok(response);
}

@GetMapping("/alarm")
public ResponseEntity<AlarmResponses> getAlarms(@LoginMember AuthInfo authInfo) {
AlarmResponses responses = alarmService.getAlarm(authInfo.getId());
return ResponseEntity.ok(responses);
}

@PostMapping("/alarm/check")
public ResponseEntity<Void> checkAlarm(@LoginMember AuthInfo authInfo, @RequestBody AlarmCheckRequest request) {
alarmService.checkAlarm(authInfo.getId(), request);
return ResponseEntity.ok()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package corea.alarm.controller;

import corea.alarm.dto.AlarmCheckRequest;
import corea.alarm.dto.AlarmCountResponse;
import corea.alarm.dto.AlarmResponses;
import corea.auth.domain.AuthInfo;
import corea.exception.ExceptionType;
import corea.global.annotation.ApiErrorResponses;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;

@Tag(name = "Alarm", description = "알림 관련 API")
public interface AlarmControllerSpecification {

@Operation(summary = "읽지 않은 알림 개수를 반환합니다.",
description = "자신의 마이페이지에 디스플레이 되는 프로필 정보를 작성합니다. <br>" +
"요청 시 `Authorization Header`에 `Bearer JWT token`을 포함시켜야 합니다. " +
"이 토큰을 기반으로 `AuthInfo` 객체가 생성되며 사용자의 정보가 자동으로 주입됩니다. <br>" +
"JWT 토큰에서 추출된 사용자 정보는 피드백 작성에 필요한 인증된 사용자 정보를 제공합니다. " +
"<br><br>**참고:** 이 API를 사용하기 위해서는 유효한 JWT 토큰이 필요하며, " +
"토큰이 없거나 유효하지 않은 경우 인증 오류가 발생합니다.")
@ApiErrorResponses(value = ExceptionType.MEMBER_NOT_FOUND)
ResponseEntity<AlarmCountResponse> getAlarmCount(AuthInfo authInfo);

@Operation(summary = "알림 목록을 최신순으로 반환합니다.",
description = "자신의 마이페이지에 디스플레이 되는 프로필 정보를 작성합니다. <br>" +
"요청 시 `Authorization Header`에 `Bearer JWT token`을 포함시켜야 합니다. " +
"이 토큰을 기반으로 `AuthInfo` 객체가 생성되며 사용자의 정보가 자동으로 주입됩니다. <br>" +
"JWT 토큰에서 추출된 사용자 정보는 피드백 작성에 필요한 인증된 사용자 정보를 제공합니다. " +
"<br><br>**참고:** 이 API를 사용하기 위해서는 유효한 JWT 토큰이 필요하며, " +
"토큰이 없거나 유효하지 않은 경우 인증 오류가 발생합니다.")
@ApiErrorResponses(value = ExceptionType.MEMBER_NOT_FOUND)
ResponseEntity<AlarmResponses> getAlarms(AuthInfo authInfo);

@Operation(summary = "알림을 체크합니다.",
description = "자신의 마이페이지에 디스플레이 되는 프로필 정보를 작성합니다. <br>" +
"요청 시 `Authorization Header`에 `Bearer JWT token`을 포함시켜야 합니다. " +
"이 토큰을 기반으로 `AuthInfo` 객체가 생성되며 사용자의 정보가 자동으로 주입됩니다. <br>" +
"JWT 토큰에서 추출된 사용자 정보는 피드백 작성에 필요한 인증된 사용자 정보를 제공합니다. " +
"<br><br>**참고:** 이 API를 사용하기 위해서는 유효한 JWT 토큰이 필요하며, " +
"토큰이 없거나 유효하지 않은 경우 인증 오류가 발생합니다.")
@ApiErrorResponses(value = {ExceptionType.MEMBER_NOT_FOUND, ExceptionType.NOT_RECEIVED_ALARM})
ResponseEntity<Void> checkAlarm(AuthInfo authInfo, AlarmCheckRequest request);
}
6 changes: 6 additions & 0 deletions backend/src/main/java/corea/alarm/domain/AlarmActionType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package corea.alarm.domain;

public enum AlarmActionType {
REVIEW_COMPLETE,
REVIEW_URGE,
}
10 changes: 10 additions & 0 deletions backend/src/main/java/corea/alarm/domain/AlarmType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package corea.alarm.domain;

public enum AlarmType {
// 유저간 상호작용으로 인한 알람
USER;

public static AlarmType from(String value) {
return AlarmType.valueOf(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package corea.alarm.domain;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public record UserAlarmsByActionType(Map<AlarmActionType, List<UserToUserAlarm>> data) {
public Set<Long> getActorIds() {
return data.values()
.stream()
.flatMap(Collection::stream)
.map(UserToUserAlarm::getActorId)
.collect(Collectors.toSet());
}

public Set<Long> getRoomIds() {
return data.values()
.stream()
.flatMap(Collection::stream)
//.filter(alarm -> alarm.getAlarmActionType() == AlarmActionType.REVIEW_COMPLETE)
.map(UserToUserAlarm::getInteractionId)
.collect(Collectors.toSet());
}

public List<UserToUserAlarm> getList() {
return data.values()
.stream()
.flatMap(Collection::stream)
.toList();
}
}
62 changes: 62 additions & 0 deletions backend/src/main/java/corea/alarm/domain/UserToUserAlarm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package corea.alarm.domain;

import corea.global.BaseTimeEntity;
import corea.member.domain.Member;
import jakarta.persistence.*;
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 UserToUserAlarm extends BaseTimeEntity {

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

@Enumerated(EnumType.STRING)
private AlarmActionType alarmActionType;

/**
* C 라는 방에서 A 가 코드 리뷰를 B 에게 완료했다.
* A 의 ID 가 actorId, B 의 ID 가 receiverId
* C 의 ID 가 interactionId
*/
private Long actorId;

private Long receiverId;

private Long interactionId;

private boolean isRead;

public UserToUserAlarm(AlarmActionType alarmActionType, long actorId, long receiverId, long interactionId, boolean isRead) {
this(null, alarmActionType, actorId, receiverId, interactionId, isRead);
}

public boolean isStatus(boolean status) {
return isRead == status;
}

public String getActionType() {
return alarmActionType.name();
}

public boolean isNotReceiver(Member member) {
return !receiverId.equals(member.getId());
}

public void read() {
isRead = true;
}

public boolean isUrgeAlarm() {
return alarmActionType == AlarmActionType.REVIEW_URGE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package corea.alarm.domain;

import corea.exception.CoreaException;
import corea.exception.ExceptionType;
import corea.global.annotation.Reader;
import corea.member.domain.Member;
import lombok.RequiredArgsConstructor;

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

@Reader
@RequiredArgsConstructor
public class UserToUserAlarmReader {

private final UserToUserAlarmRepository userToUserAlarmRepository;

public long countReceivedAlarm(Member member, boolean isRead) {
return userToUserAlarmRepository.findAllByReceiverId(member.getId())
.stream()
.filter(alarm -> alarm.isStatus(isRead))
.count();
}

public UserToUserAlarm find(long actionId) {
return userToUserAlarmRepository.findById(actionId)
.orElseThrow(() -> new CoreaException(ExceptionType.NOT_RECEIVED_ALARM));
}

public UserAlarmsByActionType findAllByReceiver(Member member) {
return new UserAlarmsByActionType(userToUserAlarmRepository.findAllByReceiverId(member.getId())
.stream()
.collect(Collectors.groupingBy(
UserToUserAlarm::getAlarmActionType,
() -> new EnumMap<>(AlarmActionType.class),
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
@@ -0,0 +1,12 @@
package corea.alarm.domain;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface UserToUserAlarmRepository extends JpaRepository<UserToUserAlarm, Long> {

List<UserToUserAlarm> findAllByReceiverId(long receiverId);

List<UserToUserAlarm> findAllByActorIdAndReceiverIdAndInteractionId(long actorId, long receiverId, long interactionId);
}
Loading

0 comments on commit 271fb1d

Please sign in to comment.