Skip to content

Commit 09cc09f

Browse files
Merge pull request #217 from prgrms-web-devcourse-final-project/develop
chore: develop → main 브랜치 머지
2 parents 30f1a9c + 09961aa commit 09cc09f

File tree

60 files changed

+982
-202
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+982
-202
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package grep.neogulcoder.domain.alram.controller;
2+
3+
import grep.neogulcoder.domain.alram.controller.dto.response.AlarmResponse;
4+
import grep.neogulcoder.domain.alram.service.AlarmService;
5+
import grep.neogulcoder.global.auth.Principal;
6+
import grep.neogulcoder.global.response.ApiResponse;
7+
import java.util.List;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.PathVariable;
12+
import org.springframework.web.bind.annotation.PostMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
@RestController
17+
@RequiredArgsConstructor
18+
@RequestMapping("/api/alarm")
19+
public class AlarmController implements AlarmSpecification {
20+
21+
private final AlarmService alarmService;
22+
23+
@GetMapping("/my")
24+
public ApiResponse<List<AlarmResponse>> getAllAlarm(
25+
@AuthenticationPrincipal Principal userDetails) {
26+
return ApiResponse.success(alarmService.getAllAlarms(userDetails.getUserId()));
27+
}
28+
29+
@PostMapping("/my/check/all")
30+
public ApiResponse<Void> checkAlarm(@AuthenticationPrincipal Principal userDetails) {
31+
alarmService.checkAllAlarm(userDetails.getUserId());
32+
return ApiResponse.noContent();
33+
}
34+
35+
@PostMapping("/choose/{alarmId}/response")
36+
public ApiResponse<Void> respondToInvite(@AuthenticationPrincipal Principal principal,
37+
@PathVariable Long alarmId,
38+
boolean accepted) {
39+
if (accepted) {
40+
alarmService.acceptInvite(principal.getUserId(), alarmId);
41+
} else {
42+
alarmService.rejectInvite(principal.getUserId());
43+
}
44+
45+
return ApiResponse.success(accepted ? "스터디 초대를 수락했습니다." : "스터디 초대를 거절했습니다.");
46+
}
47+
48+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package grep.neogulcoder.domain.alram.controller;
2+
3+
import grep.neogulcoder.domain.alram.controller.dto.response.AlarmResponse;
4+
import grep.neogulcoder.global.auth.Principal;
5+
import grep.neogulcoder.global.response.ApiResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.tags.Tag;
8+
import java.util.List;
9+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
10+
11+
@Tag(name = "Alarm", description = "알림 관련 API 명세")
12+
public interface AlarmSpecification {
13+
14+
@Operation(summary = "내 알림 목록 조회", description = "로그인한 사용자의 알림 목록을 조회합니다.")
15+
ApiResponse<List<AlarmResponse>> getAllAlarm(@AuthenticationPrincipal Principal userDetails);
16+
17+
@Operation(summary = "내 알림 전체 읽음 처리", description = "로그인한 사용자의 모든 알림을 읽음 처리합니다.")
18+
ApiResponse<Void> checkAlarm(@AuthenticationPrincipal Principal userDetails);
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package grep.neogulcoder.domain.alram.controller.dto.response;
2+
3+
import grep.neogulcoder.domain.alram.entity.Alarm;
4+
import grep.neogulcoder.domain.alram.type.AlarmType;
5+
import grep.neogulcoder.domain.alram.type.DomainType;
6+
import lombok.Builder;
7+
import lombok.Data;
8+
9+
@Data
10+
public class AlarmResponse {
11+
12+
private Long id;
13+
14+
private Long receiverUserId;
15+
16+
private AlarmType alarmType;
17+
18+
private DomainType domainType;
19+
20+
private Long domainId;
21+
22+
private String message;
23+
24+
public static AlarmResponse toResponse(Long id, Long receiverUserId, AlarmType alarmType, DomainType domainType,
25+
Long domainId, String message) {
26+
return AlarmResponse.builder()
27+
.id(id)
28+
.receiverUserId(receiverUserId)
29+
.alarmType(alarmType)
30+
.domainType(domainType)
31+
.domainId(domainId)
32+
.message(message)
33+
.build();
34+
}
35+
36+
@Builder
37+
private AlarmResponse(Long id, Long receiverUserId, AlarmType alarmType, DomainType domainType,
38+
Long domainId, String message) {
39+
this.id = id;
40+
this.receiverUserId = receiverUserId;
41+
this.alarmType = alarmType;
42+
this.domainType = domainType;
43+
this.domainId = domainId;
44+
this.message = message;
45+
}
46+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package grep.neogulcoder.domain.alram.entity;
2+
3+
import grep.neogulcoder.domain.alram.type.AlarmType;
4+
import grep.neogulcoder.domain.alram.type.DomainType;
5+
import grep.neogulcoder.global.entity.BaseEntity;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.EnumType;
9+
import jakarta.persistence.Enumerated;
10+
import jakarta.persistence.GeneratedValue;
11+
import jakarta.persistence.GenerationType;
12+
import jakarta.persistence.Id;
13+
import lombok.Builder;
14+
import lombok.Getter;
15+
16+
@Entity
17+
@Getter
18+
@Schema(description = "알림 정보")
19+
public class Alarm extends BaseEntity {
20+
21+
@Id
22+
@GeneratedValue(strategy = GenerationType.IDENTITY)
23+
private Long id;
24+
25+
private Long receiverUserId;
26+
27+
@Enumerated(EnumType.STRING)
28+
private AlarmType alarmType;
29+
30+
@Enumerated(EnumType.STRING)
31+
private DomainType domainType;
32+
33+
private Long domainId;
34+
35+
private String message;
36+
37+
private boolean checked = false;
38+
39+
public void checkAlarm() {
40+
this.checked = true;
41+
}
42+
43+
public static Alarm init(AlarmType alarmType, Long receiverUserId , DomainType domainType, Long domainId, String message) {
44+
return Alarm.builder()
45+
.alarmType(alarmType)
46+
.receiverUserId(receiverUserId)
47+
.domainType(domainType)
48+
.domainId(domainId)
49+
.message(message)
50+
.build();
51+
}
52+
53+
@Builder
54+
private Alarm(Long id, Long receiverUserId, AlarmType alarmType, DomainType domainType,
55+
Long domainId, String message) {
56+
this.id = id;
57+
this.receiverUserId = receiverUserId;
58+
this.alarmType = alarmType;
59+
this.domainType = domainType;
60+
this.domainId = domainId;
61+
this.message = message;
62+
}
63+
64+
protected Alarm() {
65+
}
66+
67+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package grep.neogulcoder.domain.alram.exception.code;
2+
3+
import grep.neogulcoder.global.response.code.ErrorCode;
4+
import lombok.Getter;
5+
import org.springframework.http.HttpStatus;
6+
7+
@Getter
8+
public enum AlarmErrorCode implements ErrorCode {
9+
10+
ALARM_NOT_FOUND("A001",HttpStatus.NOT_FOUND,"알람을 찾을 수 없습니다.");
11+
12+
13+
private final String code;
14+
private final HttpStatus status;
15+
private final String message;
16+
17+
AlarmErrorCode(String code, HttpStatus status, String message) {
18+
this.code = code;
19+
this.status = status;
20+
this.message = message;
21+
}
22+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package grep.neogulcoder.domain.alram.repository;
2+
3+
import grep.neogulcoder.domain.alram.entity.Alarm;
4+
import java.util.List;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
public interface AlarmRepository extends JpaRepository<Alarm, Long> {
8+
List<Alarm> findAllByReceiverUserIdAndCheckedFalse(Long receiverUserId);
9+
10+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package grep.neogulcoder.domain.alram.service;
2+
3+
import grep.neogulcoder.domain.alram.controller.dto.response.AlarmResponse;
4+
import grep.neogulcoder.domain.alram.entity.Alarm;
5+
import grep.neogulcoder.domain.alram.exception.code.AlarmErrorCode;
6+
import grep.neogulcoder.domain.alram.repository.AlarmRepository;
7+
import grep.neogulcoder.domain.alram.type.AlarmType;
8+
import grep.neogulcoder.domain.alram.type.DomainType;
9+
import grep.neogulcoder.domain.study.Study;
10+
import grep.neogulcoder.domain.study.StudyMember;
11+
import grep.neogulcoder.domain.study.event.StudyInviteEvent;
12+
import grep.neogulcoder.domain.study.repository.StudyMemberQueryRepository;
13+
import grep.neogulcoder.domain.study.repository.StudyRepository;
14+
import grep.neogulcoder.global.exception.business.BusinessException;
15+
import grep.neogulcoder.global.exception.business.NotFoundException;
16+
import grep.neogulcoder.global.provider.finder.MessageFinder;
17+
import java.util.List;
18+
import lombok.RequiredArgsConstructor;
19+
import org.springframework.context.event.EventListener;
20+
import org.springframework.stereotype.Service;
21+
import org.springframework.transaction.annotation.Transactional;
22+
23+
import static grep.neogulcoder.domain.study.exception.code.StudyErrorCode.STUDY_NOT_FOUND;
24+
import static grep.neogulcoder.domain.studyapplication.exception.code.ApplicationErrorCode.APPLICATION_PARTICIPANT_LIMIT_EXCEEDED;
25+
26+
@Service
27+
@RequiredArgsConstructor
28+
@Transactional(readOnly = true)
29+
public class AlarmService {
30+
31+
private final AlarmRepository alarmRepository;
32+
private final MessageFinder messageFinder;
33+
private final StudyRepository studyRepository;
34+
private final StudyMemberQueryRepository studyMemberQueryRepository;
35+
36+
@Transactional
37+
public void saveAlarm(Long receiverId, AlarmType alarmType, DomainType domainType, Long domainId) {
38+
String message = messageFinder.findMessage(alarmType, domainType, domainId);
39+
alarmRepository.save(Alarm.init(alarmType, receiverId, domainType, domainId, message));
40+
}
41+
42+
public List<AlarmResponse> getAllAlarms(Long receiverUserId) {
43+
return alarmRepository.findAllByReceiverUserIdAndCheckedFalse(receiverUserId).stream()
44+
.map(alarm -> AlarmResponse.toResponse(
45+
alarm.getId(),
46+
alarm.getReceiverUserId(),
47+
alarm.getAlarmType(),
48+
alarm.getDomainType(),
49+
alarm.getDomainId(),
50+
alarm.getMessage()))
51+
.toList();
52+
}
53+
54+
@Transactional
55+
public void checkAllAlarm(Long receiverUserId) {
56+
List<Alarm> alarms = alarmRepository.findAllByReceiverUserIdAndCheckedFalse(receiverUserId);
57+
alarms.stream()
58+
.filter(alarm -> alarm.getAlarmType() != AlarmType.INVITE)
59+
.forEach(Alarm::checkAlarm);
60+
}
61+
62+
@EventListener
63+
public void handleStudyInviteEvent(StudyInviteEvent event) {
64+
saveAlarm(
65+
event.targetUserId(),
66+
AlarmType.INVITE,
67+
DomainType.STUDY,
68+
event.studyId()
69+
);
70+
}
71+
72+
@Transactional
73+
public void acceptInvite(Long targetUserId, Long alarmId) {
74+
75+
validateParticipantStudyLimit(targetUserId);
76+
77+
Alarm alarm = findValidAlarm(alarmId);
78+
Long studyId = alarm.getDomainId();
79+
Study study = findValidStudy(studyId);
80+
StudyMember.createMember(study,targetUserId);
81+
alarm.checkAlarm();
82+
}
83+
84+
@Transactional
85+
public void rejectInvite(Long alarmId) {
86+
Alarm alarm = findValidAlarm(alarmId);
87+
alarm.checkAlarm();
88+
}
89+
90+
private Alarm findValidAlarm(Long alarmId) {
91+
return alarmRepository.findById(alarmId).orElseThrow(() -> new NotFoundException(AlarmErrorCode.ALARM_NOT_FOUND));
92+
}
93+
94+
private Study findValidStudy(Long studyId) {
95+
return studyRepository.findById(studyId)
96+
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
97+
}
98+
99+
private void validateParticipantStudyLimit(Long userId) {
100+
int count = studyMemberQueryRepository.countActiveUnfinishedStudies(userId);
101+
if (count >= 10) {
102+
throw new BusinessException(APPLICATION_PARTICIPANT_LIMIT_EXCEEDED);
103+
}
104+
}
105+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package grep.neogulcoder.domain.alram.type;
2+
3+
public enum AlarmType {
4+
5+
INVITE
6+
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package grep.neogulcoder.domain.alram.type;
2+
3+
public enum DomainType {
4+
STUDY
5+
}

src/main/java/grep/neogulcoder/domain/buddy/entity/BuddyEnergy.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,11 @@ public void updateEnergy(ReviewType reviewType) {
4848
this.level -= 1;
4949
}
5050
}
51+
52+
public BuddyEnergyReason findReasonFrom(ReviewType reviewType) {
53+
if(reviewType.isPositive()){
54+
return BuddyEnergyReason.POSITIVE_REVIEW;
55+
}
56+
return BuddyEnergyReason.NEGATIVE_REVIEW;
57+
}
5158
}

0 commit comments

Comments
 (0)