Skip to content

Commit 6bb6589

Browse files
committed
feat: 알림 api 구현
1 parent aefa5dd commit 6bb6589

12 files changed

Lines changed: 255 additions & 2 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package everTale.everTale_be.domain.alarm.controller;
2+
3+
import everTale.everTale_be.domain.alarm.dto.response.AlarmListResponseDto;
4+
import everTale.everTale_be.domain.alarm.service.AlarmService;
5+
import everTale.everTale_be.global.apiPayload.ApiResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.Parameter;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.data.domain.Pageable;
11+
import org.springframework.data.web.PageableDefault;
12+
import org.springframework.web.bind.annotation.*;
13+
14+
@RestController
15+
@RequiredArgsConstructor
16+
@RequestMapping("/alarms")
17+
@Tag(name = "Alarm", description = "알림 API")
18+
public class AlarmController {
19+
20+
private final AlarmService alarmService;
21+
22+
@Operation(summary = "알림 조회 API", description = "사용자의 알림 목록을 8개씩 조회합니다.")
23+
@GetMapping
24+
public ApiResponse<AlarmListResponseDto> getAlarms(@PageableDefault(size = 8) Pageable pageable){
25+
AlarmListResponseDto responseDto = alarmService.getAlarms(pageable);
26+
return ApiResponse.onSuccess(responseDto);
27+
}
28+
29+
@Operation(summary = "알림 읽음 처리", description = "해당 alarmId에 해당하는 알림을 읽음 처리합니다.")
30+
@PostMapping("{alarmId}")
31+
public ApiResponse<String> readAlarm(@Parameter(description = "알림 ID") @PathVariable("alarmId") Long alarmId){
32+
alarmService.readAlarm(alarmId);
33+
return ApiResponse.onSuccess("알림 읽음 처리가 되었습니다.");
34+
}
35+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package everTale.everTale_be.domain.alarm.dto;
2+
3+
import everTale.everTale_be.domain.alarm.entity.Alarm;
4+
import everTale.everTale_be.domain.alarm.entity.Enum.AlarmType;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
9+
@Getter
10+
@Builder
11+
@Schema(description = "알림 요약 정보")
12+
public class AlarmSummary {
13+
14+
@Schema(description = "알림 ID", example = "1")
15+
private Long alarmId;
16+
17+
@Schema(description = "알림 타입", example = "EASTEREGG_VOICE")
18+
private AlarmType alarmType;
19+
20+
@Schema(description = "스토리 ID", example = "12")
21+
private Long storyId;
22+
23+
@Schema(description = "읽음 여부", example = "0")
24+
private boolean isRead;
25+
26+
public static AlarmSummary from(Alarm alarm){
27+
return AlarmSummary.builder()
28+
.alarmId(alarm.getId())
29+
.alarmType(alarm.getAlarmType())
30+
.storyId(alarm.getStory().getId())
31+
.isRead(alarm.isRead())
32+
.build();
33+
}
34+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package everTale.everTale_be.domain.alarm.dto.response;
2+
3+
import everTale.everTale_be.domain.alarm.dto.AlarmSummary;
4+
import everTale.everTale_be.domain.alarm.entity.Alarm;
5+
import everTale.everTale_be.domain.story.dto.StoryCollectionResponseDto;
6+
import everTale.everTale_be.domain.story.dto.StorySummary;
7+
import everTale.everTale_be.domain.story.entity.Story;
8+
import io.swagger.v3.oas.annotations.media.Schema;
9+
import lombok.Builder;
10+
import lombok.Getter;
11+
import org.springframework.data.domain.Page;
12+
13+
import java.util.List;
14+
15+
@Getter
16+
@Builder
17+
@Schema(description = "알림 목록 응답 DTO")
18+
public class AlarmListResponseDto {
19+
20+
@Schema(description = "알림 요약 리스트")
21+
private List<AlarmSummary> alarmSummaries;
22+
23+
@Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
24+
private int currentPage;
25+
26+
@Schema(description = "전체 페이지 수", example = "5")
27+
private int totalPage;
28+
29+
@Schema(description = "전체 스토리 개수", example = "123")
30+
private long totalCount;
31+
32+
public static AlarmListResponseDto from(Page<Alarm> alarms){
33+
return AlarmListResponseDto.builder()
34+
.alarmSummaries(alarms.stream().map(AlarmSummary::from).toList())
35+
.currentPage(alarms.getNumber())
36+
.totalPage(alarms.getTotalPages())
37+
.totalCount(alarms.getTotalElements())
38+
.build();
39+
}
40+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package everTale.everTale_be.domain.alarm.entity;
2+
3+
import everTale.everTale_be.domain.alarm.entity.Enum.AlarmType;
4+
import everTale.everTale_be.domain.profile.entity.Profile;
5+
import everTale.everTale_be.domain.story.entity.Story;
6+
import jakarta.persistence.*;
7+
import lombok.AccessLevel;
8+
import lombok.Builder;
9+
import lombok.Getter;
10+
import lombok.NoArgsConstructor;
11+
import org.springframework.data.annotation.CreatedDate;
12+
13+
import java.time.LocalDateTime;
14+
15+
@Entity
16+
@Getter
17+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
18+
public class Alarm {
19+
20+
@Id
21+
@GeneratedValue(strategy = GenerationType.IDENTITY)
22+
private Long id;
23+
24+
@Enumerated(EnumType.STRING)
25+
private AlarmType alarmType;
26+
27+
@Column(nullable = false)
28+
private boolean isRead = false;
29+
30+
@CreatedDate
31+
@Column(name="created_at", updatable = false)
32+
private LocalDateTime createdAt;
33+
34+
@ManyToOne(fetch = FetchType.LAZY)
35+
@JoinColumn(name = "profile_id", updatable = false, nullable = false)
36+
private Profile profile;
37+
38+
@ManyToOne(fetch = FetchType.LAZY)
39+
@JoinColumn(name = "story_id", updatable = false, nullable = false)
40+
private Story story;
41+
42+
@Builder
43+
public Alarm(AlarmType alarmType,
44+
Profile profile,
45+
Story story) {
46+
this.alarmType = alarmType;
47+
this.profile = profile;
48+
this.story = story;
49+
}
50+
51+
public void setRead(){
52+
isRead = true;
53+
}
54+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package everTale.everTale_be.domain.alarm.entity.Enum;
2+
3+
public enum AlarmType {
4+
EASTEREGG_LETTER, EASTEREGG_VOICE
5+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package everTale.everTale_be.domain.alarm.repository;
2+
3+
import everTale.everTale_be.domain.alarm.entity.Alarm;
4+
import org.springframework.data.domain.Page;
5+
import org.springframework.data.domain.Pageable;
6+
import org.springframework.data.jpa.repository.JpaRepository;
7+
8+
import java.util.Optional;
9+
10+
public interface AlarmRepository extends JpaRepository<Alarm, Long> {
11+
12+
Optional<Alarm> findByIdAndProfileId(Long id, Long profileId);
13+
14+
Page<Alarm> findByProfileId(Long profileId, Pageable pageable);
15+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package everTale.everTale_be.domain.alarm.service;
2+
3+
import everTale.everTale_be.domain.alarm.dto.response.AlarmListResponseDto;
4+
import everTale.everTale_be.domain.alarm.entity.Alarm;
5+
import everTale.everTale_be.domain.alarm.entity.Enum.AlarmType;
6+
import everTale.everTale_be.domain.alarm.repository.AlarmRepository;
7+
import everTale.everTale_be.domain.profile.entity.Profile;
8+
import everTale.everTale_be.domain.profile.util.ProfileHelper;
9+
import everTale.everTale_be.domain.story.entity.Story;
10+
import everTale.everTale_be.global.apiPayload.code.status.ErrorStatus;
11+
import everTale.everTale_be.global.apiPayload.exception.handler.UnAuthorizedHandler;
12+
import lombok.AllArgsConstructor;
13+
import org.springframework.data.domain.Page;
14+
import org.springframework.data.domain.Pageable;
15+
import org.springframework.stereotype.Service;
16+
import org.springframework.transaction.annotation.Transactional;
17+
18+
@Service
19+
@AllArgsConstructor
20+
@Transactional(readOnly = true)
21+
public class AlarmService {
22+
23+
private final AlarmRepository alarmRepository;
24+
private final ProfileHelper profileHelper;
25+
26+
@Transactional
27+
public void createAlarm(AlarmType alarmType, Profile profile, Story story){
28+
Alarm alarm = Alarm.builder()
29+
.alarmType(alarmType)
30+
.profile(profile)
31+
.story(story)
32+
.build();
33+
alarmRepository.save(alarm);
34+
}
35+
36+
public AlarmListResponseDto getAlarms(Pageable pageable){
37+
Long profileId = profileHelper.getAuthenticatedProfileId();
38+
Page<Alarm> alarms = alarmRepository.findByProfileId(profileId, pageable);
39+
return AlarmListResponseDto.from(alarms);
40+
}
41+
42+
@Transactional
43+
public void readAlarm(Long alarmId){
44+
Long profileId = profileHelper.getAuthenticatedProfileId();
45+
46+
Alarm alarm = alarmRepository.findByIdAndProfileId(alarmId, profileId)
47+
.orElseThrow(() -> new UnAuthorizedHandler(ErrorStatus.UNAUTHORIZED_PROFILE_ACCESS));
48+
alarm.setRead();
49+
}
50+
}

src/main/java/everTale/everTale_be/domain/easterEgg/service/EasterEggLetterService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package everTale.everTale_be.domain.easterEgg.service;
22

3+
import everTale.everTale_be.domain.alarm.entity.Enum.AlarmType;
4+
import everTale.everTale_be.domain.alarm.service.AlarmService;
35
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterRequestDTO;
46
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterResponseDTO;
57
import everTale.everTale_be.domain.easterEgg.dto.easterEggLetter.EasterEggLetterStoriesResponseDto;
@@ -32,11 +34,13 @@ public class EasterEggLetterService {
3234
private final ProfileHelper profileHelper;
3335
private final EasterEggLetterRepository easterEggLetterRepository;
3436
private final ProfileService profileService;
37+
private final AlarmService alarmService;
3538

3639
// 이스터에그 편지 생성
3740
@Transactional
3841
public void saveEasterEggLetter(Long storyId, EasterEggLetterRequestDTO.EasterEggLetterCreateRequestDTO request) {
3942
Story story = getStoryForParent(storyId);
43+
Profile profile = story.getProfile();
4044

4145
// 이미 편지가 존재하는 경우 예외
4246
if (story.getEasterEggLetter() != null) {
@@ -50,6 +54,7 @@ public void saveEasterEggLetter(Long storyId, EasterEggLetterRequestDTO.EasterEg
5054
.availableAt(request.getAvailableAt())
5155
.build();
5256
story.addEasterEggLetter(letter);
57+
alarmService.createAlarm(AlarmType.EASTEREGG_VOICE, profile, story);
5358
}
5459

5560
// 이스터에그 편지 조회

src/main/java/everTale/everTale_be/domain/easterEgg/service/EasterEggVoiceService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package everTale.everTale_be.domain.easterEgg.service;
22

3+
import everTale.everTale_be.domain.alarm.entity.Enum.AlarmType;
4+
import everTale.everTale_be.domain.alarm.service.AlarmService;
35
import everTale.everTale_be.domain.easterEgg.dto.easterEggVoice.request.EasterEggVoiceRegisterRequestDto;
46
import everTale.everTale_be.domain.easterEgg.dto.easterEggVoice.request.EasterEggVoiceRequestDto;
57
import everTale.everTale_be.domain.easterEgg.dto.easterEggVoice.response.EasterEggVoiceStoriesResponseDto;
@@ -36,6 +38,7 @@ public class EasterEggVoiceService {
3638
private final S3Manager s3Manager;
3739
private final SceneRepository sceneRepository;
3840
private final EasterEggVoiceRepository easterEggVoiceRepository;
41+
private final AlarmService alarmService;
3942
private final ProfileService profileService;
4043
private final YoloApiClient yoloApiClient;
4144

@@ -52,6 +55,8 @@ public YoloDetectionResponseDto getObjectFromImages(Long storyId){
5255
public void createEasterEggVoice(Long storyId, MultipartFile voiceFile, EasterEggVoiceRegisterRequestDto requestDto){
5356
Scene scene = sceneRepository.findByStoryIdAndPage(storyId, requestDto.getIndex())
5457
.orElseThrow(()-> new NotFoundHandler(ErrorStatus.SCENE_NOT_FOUND));
58+
Story story = scene.getStory();
59+
Profile profile = story.getProfile();
5560

5661
String voiceUrl = s3Manager.uploadFile(voiceFile, "eastereggs/audios");
5762
EasterEggVoice voice = EasterEggVoice.builder()
@@ -63,6 +68,7 @@ public void createEasterEggVoice(Long storyId, MultipartFile voiceFile, EasterEg
6368
.voiceFile(voiceUrl)
6469
.build();
6570
easterEggVoiceRepository.save(voice);
71+
alarmService.createAlarm(AlarmType.EASTEREGG_VOICE, profile, story);
6672
}
6773

6874
@Transactional

src/main/java/everTale/everTale_be/domain/profile/entity/Profile.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package everTale.everTale_be.domain.profile.entity;
22

3+
import everTale.everTale_be.domain.alarm.entity.Alarm;
34
import everTale.everTale_be.domain.profile.entity.Enum.ProfileStatus;
45
import everTale.everTale_be.domain.profile.entity.Enum.ProfileType;
56
import everTale.everTale_be.domain.profile.dto.request.ChildProfileUpdateRequestDto;
@@ -61,8 +62,8 @@ public class Profile extends BaseTimeEntity {
6162
@OneToMany(mappedBy = "profile")
6263
private List<Story> stories = new ArrayList<>();
6364

64-
// @OneToMany(mappedBy = "alarm", cascade = CascadeType.ALL, orphanRemoval = true)
65-
// private List<Alram> alrams = new ArrayList<>();
65+
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval = true)
66+
private List<Alarm> alarms = new ArrayList<>();
6667

6768
@Builder
6869
public Profile(String name,

0 commit comments

Comments
 (0)