Skip to content

Commit 2160dee

Browse files
authored
✨ refactor: 알림 리팩터링 및 테스트 (#31)
1 parent 9b0e6ab commit 2160dee

34 files changed

Lines changed: 1056 additions & 338 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@ build/generated/
4141
application-local.properties
4242
application-test.properties
4343
application-s3.properties
44-
test_img_dir
44+
test_img_dir
45+
46+
**/src/main/resources/secret

build.gradle

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ dependencies {
5858
testCompileOnly 'org.projectlombok:lombok'
5959
testAnnotationProcessor 'org.projectlombok:lombok'
6060
testRuntimeOnly 'com.h2database:h2'
61+
testImplementation 'org.awaitility:awaitility:4.2.0'
6162
}
6263

6364
sourceSets {
@@ -70,4 +71,10 @@ sourceSets {
7071

7172
tasks.named('test') {
7273
useJUnitPlatform()
73-
}
74+
}
75+
76+
test {
77+
jvmArgs += [
78+
'--add-opens=java.base/java.lang=ALL-UNNAMED'
79+
]
80+
}
Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
11
package com.example.moim.external.fcm;
22

3+
import com.google.auth.oauth2.GoogleCredentials;
4+
import com.google.firebase.FirebaseApp;
5+
import com.google.firebase.FirebaseOptions;
6+
import com.google.firebase.messaging.FirebaseMessaging;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.util.List;
10+
import org.springframework.context.annotation.Bean;
311
import org.springframework.context.annotation.Configuration;
12+
import org.springframework.core.io.ClassPathResource;
413

514
@Configuration
615
public class FcmConfig {
716

8-
// @Bean
9-
// FirebaseMessaging firebaseMessaging() throws IOException {
10-
// ClassPathResource resource = new ClassPathResource("firebase/meta-gachon-fcm-firebase-adminsdk-fyr7b-30929b486f.json");
11-
// InputStream refreshToken = resource.getInputStream();
12-
//
13-
// FirebaseApp firebaseApp = null;
14-
// List<FirebaseApp> firebaseAppList = FirebaseApp.getApps();
15-
//
16-
// if (firebaseAppList != null && !firebaseAppList.isEmpty()) {
17-
// for (FirebaseApp app : firebaseAppList) {
18-
// if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) {
19-
// firebaseApp = app;
20-
// }
21-
// }
22-
// } else {
23-
// FirebaseOptions options = FirebaseOptions.builder()
24-
// .setCredentials(GoogleCredentials.fromStream(refreshToken))
25-
// .build();
26-
//
27-
// firebaseApp = FirebaseApp.initializeApp(options);
28-
// }
29-
//
30-
// return FirebaseMessaging.getInstance(firebaseApp);
31-
// }
17+
@Bean
18+
FirebaseMessaging firebaseMessaging() throws IOException {
19+
ClassPathResource resource = new ClassPathResource("secret/sample-firebase-test-f3c27-firebase-adminsdk-fbsvc-c15b4b66c6.json");
20+
InputStream refreshToken = resource.getInputStream();
21+
22+
FirebaseApp firebaseApp = null;
23+
List<FirebaseApp> firebaseAppList = FirebaseApp.getApps();
24+
25+
if (firebaseAppList != null && !firebaseAppList.isEmpty()) {
26+
for (FirebaseApp app : firebaseAppList) {
27+
if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) {
28+
firebaseApp = app;
29+
}
30+
}
31+
} else {
32+
FirebaseOptions options = FirebaseOptions.builder()
33+
.setCredentials(GoogleCredentials.fromStream(refreshToken))
34+
.build();
35+
36+
firebaseApp = FirebaseApp.initializeApp(options);
37+
}
38+
39+
return FirebaseMessaging.getInstance(firebaseApp);
40+
}
3241
}
Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
package com.example.moim.notification.controller;
22

3+
import com.example.moim.notification.controller.port.NotificationService;
34
import com.example.moim.notification.dto.NotificationExistOutput;
45
import com.example.moim.notification.dto.NotificationOutput;
5-
import com.example.moim.notification.service.NotificationService;
66
import com.example.moim.user.dto.UserDetailsImpl;
7+
import java.util.List;
78
import lombok.RequiredArgsConstructor;
89
import org.springframework.security.core.annotation.AuthenticationPrincipal;
910
import org.springframework.web.bind.annotation.DeleteMapping;
1011
import org.springframework.web.bind.annotation.GetMapping;
1112
import org.springframework.web.bind.annotation.PathVariable;
1213
import org.springframework.web.bind.annotation.RestController;
1314

14-
import java.util.List;
15-
1615
@RestController
1716
@RequiredArgsConstructor
18-
public class NotificationController implements NotificationControllerDocs{
17+
public class NotificationController implements NotificationControllerDocs {
1918
private final NotificationService notificationService;
2019

21-
@GetMapping(value = "/notice")
22-
public NotificationExistOutput noticeCheck(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl) {
23-
return notificationService.checkNotice(userDetailsImpl.getUser());
20+
@GetMapping(value = "/notification/unread-count")
21+
public NotificationExistOutput notificationUnreadCount(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl) {
22+
return notificationService.checkUnread(userDetailsImpl.getUser());
2423
}
2524

26-
@GetMapping("/notices")
27-
public List<NotificationOutput> noticeFind(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl) {
28-
return notificationService.findNotice(userDetailsImpl.getUser());
25+
@GetMapping("/notification")
26+
public List<NotificationOutput> notificationFind(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl) {
27+
return notificationService.findAll(userDetailsImpl.getUser());
2928
}
3029

31-
@DeleteMapping("/notices/{id}")
32-
public void noticeRemove(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @PathVariable Long id) {
33-
notificationService.removeNotice(id);
30+
@DeleteMapping("/notification/{id}")
31+
public void notificationRemove(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @PathVariable Long id) {
32+
notificationService.remove(id);
3433
}
3534
}

src/main/java/com/example/moim/notification/controller/NotificationControllerDocs.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
public interface NotificationControllerDocs {
1515

1616
@Operation(summary = "새로운 알림 있는지 체크")
17-
NotificationExistOutput noticeCheck(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl);
17+
NotificationExistOutput notificationUnreadCount(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl);
1818

1919
@Operation(summary = "알림 조회")
20-
List<NotificationOutput> noticeFind(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl);
20+
List<NotificationOutput> notificationFind(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl);
2121

2222
@Operation(summary = "알림 삭제")
23-
void noticeRemove(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @PathVariable Long id);
23+
void notificationRemove(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @PathVariable Long id);
2424
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.example.moim.notification.controller.port;
2+
3+
import com.example.moim.notification.dto.NotificationExistOutput;
4+
import com.example.moim.notification.dto.NotificationOutput;
5+
import com.example.moim.notification.entity.NotificationEntity;
6+
import com.example.moim.user.entity.User;
7+
import java.util.List;
8+
9+
public interface NotificationService {
10+
NotificationExistOutput checkUnread(User user);
11+
12+
List<NotificationOutput> findAll(User user);
13+
14+
void remove(Long id);
15+
16+
void sendAll(List<NotificationEntity> notificationEntities);
17+
}
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
package com.example.moim.notification.dto;
22

3-
import com.example.moim.notification.entity.Notifications;
4-
import lombok.Data;
5-
3+
import com.example.moim.notification.entity.NotificationEntity;
64
import java.time.format.DateTimeFormatter;
5+
import lombok.Data;
76

87
@Data
98
public class NotificationOutput {
109
private Long id;
10+
private String notificationType;
1111
private String title;
12-
private String category;
1312
private String content;
14-
private String time;
13+
private String createdAt;
1514
private Boolean isRead;
15+
private String actionUrl;
1616

17-
public NotificationOutput(Notifications notifications) {
18-
this.id = notifications.getId();
19-
this.title = notifications.getTitle();
20-
this.category = notifications.getCategory();
21-
this.content = notifications.getContents();
22-
this.time = notifications.getCreatedDate().format(DateTimeFormatter.ofPattern("MM/dd HH:mm"));
23-
notifications.setRead(true);
24-
this.isRead = notifications.getIsRead();
17+
public NotificationOutput(NotificationEntity notificationEntity) {
18+
this.id = notificationEntity.getId();
19+
this.title = notificationEntity.getTitle();
20+
this.notificationType = notificationEntity.getType().name();
21+
this.content = notificationEntity.getContent();
22+
this.createdAt = notificationEntity.getCreatedDate().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"));
23+
this.isRead = notificationEntity.getIsRead();
24+
this.actionUrl = notificationEntity.getType().getCategory() + "/" + notificationEntity.getLinkedId();
2525
}
2626
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.example.moim.notification.entity;
2+
3+
import com.example.moim.global.entity.BaseEntity;
4+
import com.example.moim.user.entity.User;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.EnumType;
7+
import jakarta.persistence.Enumerated;
8+
import jakarta.persistence.FetchType;
9+
import jakarta.persistence.GeneratedValue;
10+
import jakarta.persistence.GenerationType;
11+
import jakarta.persistence.Id;
12+
import jakarta.persistence.ManyToOne;
13+
import jakarta.persistence.Table;
14+
import lombok.Builder;
15+
import lombok.Getter;
16+
import lombok.NoArgsConstructor;
17+
18+
@Entity
19+
@Getter
20+
@NoArgsConstructor
21+
@Table(name = "notifications")
22+
public class NotificationEntity extends BaseEntity {
23+
@Id
24+
@GeneratedValue(strategy = GenerationType.IDENTITY)
25+
private Long id;
26+
@ManyToOne(fetch = FetchType.LAZY)
27+
private User targetUser;
28+
@Enumerated(EnumType.STRING)
29+
private NotificationType type;
30+
private String title;
31+
private String content;
32+
private Long linkedId; // 알림과 연관된 클럽, 일정, 매치 등의 ID
33+
private Boolean isRead;
34+
private NotificationStatus status;
35+
36+
@Builder
37+
public NotificationEntity(User targetUser, NotificationType type, String title, String content, Long linkedId) {
38+
this.targetUser = targetUser;
39+
this.type = type;
40+
this.title = title;
41+
this.content = content;
42+
this.linkedId = linkedId;
43+
this.isRead = false;
44+
this.status = NotificationStatus.READY;
45+
}
46+
47+
public static NotificationEntity create(User targetUser, NotificationType type, String content, String title, Long linkedId) {
48+
return NotificationEntity.builder()
49+
.targetUser(targetUser)
50+
.type(type)
51+
.content(content)
52+
.title(title)
53+
.linkedId(linkedId)
54+
.build();
55+
}
56+
57+
public void read() {
58+
isRead = true;
59+
}
60+
61+
public void sent() {
62+
status = NotificationStatus.SENT;
63+
}
64+
65+
public void failed() {
66+
status = NotificationStatus.FAILED;
67+
}
68+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.example.moim.notification.entity;
2+
3+
public enum NotificationStatus {
4+
READY,
5+
SENT,
6+
FAILED,
7+
;
8+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.example.moim.notification.entity;
2+
3+
import java.util.IllegalFormatException;
4+
import lombok.Getter;
5+
import lombok.RequiredArgsConstructor;
6+
7+
@Getter
8+
@RequiredArgsConstructor
9+
public enum NotificationType {
10+
CLUB_JOIN("클럽 가입", "%s님이 %s에 가입했습니다."),
11+
SCHEDULE_SAVE("일정 등록", "%s 일정이 등록되었습니다. \n 참가 여부를 투표해주세요!!"),
12+
SCHEDULE_REMINDER("일정 하루 전", "내일 %s 일정이 있습니다."),
13+
SCHEDULE_ENCOURAGE("투표 독려", "%s 일정이 참가투표가 곧 마감됩니다.\n 참가 여부를 투표해주세요!!"),
14+
SCHEDULE_JOIN("일정 참여", "%s 일정에 참여했습니다."),
15+
MATCH_SCHEDULED("매치 등록", "%s 클럽 %s %s 매치가 등록되었습니다.\n 매치정보를 확인하고 신청해주세요!"),
16+
MATCH_SUCCESS("매칭 성공", "%s 클럽과의 %s %s 매치가 확정되었습니다.\n 매치정보를 다시 한 번 확인해주세요!"),
17+
MATCH_REVIEW("매치 리뷰", "%s 클럽과의 매치는 즐거우셨나요?\n %s 님의 득점 기록을 입력해주세요!"),
18+
MATCH_SUGGESTION("매치 건의", "클럽원이 %s 클럽과의 %s %s 매치를 원합니다.\n 매치 정보를 확인하고 신청해주세요!"),
19+
MATCH_REQUEST("매치 요청", "%s 클럽이 %s 매치에 신청했습니다.\n 클럽 정보를 확인하고 매치를 확정해주세요!"),
20+
MATCH_INVITE("매치 초대", "%s 클럽에서 친선 매치를 제안했습니다.\n 클럽 정보를 확인하고 매치를 확정해주세요!"),
21+
MATCH_FAILED_UNREQUESTED("매치 실패", "신청 클럽이 없어 <%s> 매치가 성사되지않았습니다 \uD83D\uDE2D\n 다음에 다시 등록해주세요!"),
22+
MATCH_FAILED_UNSELECTED("매치 실패", "<%s> 매치 등록 클럽이 다른 클럽을 선택했어요\uD83E\uDEE3\n 다음에 다시 신청해주세요!"),
23+
MATCH_CANCEL_USER("매치 취소", "<%s> 매치가 취소되었습니다.\n 다음에 다시 신청해주세요!"),
24+
MATCH_CANCEL_CLUB("매치 취소", "<%s> 매치가 취소되었습니다.\n 다음에 다시 신청해주세요!"),
25+
;
26+
27+
private final String title;
28+
private final String messageTemplate;
29+
30+
public String formatMessage(Object... args) throws IllegalFormatException {
31+
return String.format(messageTemplate, args);
32+
}
33+
34+
public String getCategory() {
35+
return name().substring(0, name().indexOf("_")).toLowerCase();
36+
}
37+
}

0 commit comments

Comments
 (0)