From 6fa4993bb951f078d6b3a7cd8af6b739fdf783db Mon Sep 17 00:00:00 2001 From: Gouyeon Chung Date: Mon, 21 Oct 2024 20:08:45 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[KKM-161]=20FEAT=20:=20Alarm=20Setting=20do?= =?UTF-8?q?main=20=EC=9E=AC=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../short_news/alarm/domain/AlarmSetting.java | 38 ------------------- .../kkokkomu/short_news/user/domain/User.java | 7 +++- 2 files changed, 6 insertions(+), 39 deletions(-) delete mode 100644 src/main/java/com/kkokkomu/short_news/alarm/domain/AlarmSetting.java diff --git a/src/main/java/com/kkokkomu/short_news/alarm/domain/AlarmSetting.java b/src/main/java/com/kkokkomu/short_news/alarm/domain/AlarmSetting.java deleted file mode 100644 index 45105c7..0000000 --- a/src/main/java/com/kkokkomu/short_news/alarm/domain/AlarmSetting.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.kkokkomu.short_news.alarm.domain; - -import com.kkokkomu.short_news.user.domain.User; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.DynamicUpdate; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DynamicUpdate -@Table(name = "alarm_setting") -public class AlarmSetting { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; // seq - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @Column(name = "generate_alarm_yn", nullable = false) - private Boolean generateAlarmYn; // 새 뉴스 생 알림 on off - - @Column(name = "reply_alarm_yn", nullable = false) - private Boolean replyAlarmYn; // 대댓글 알림 on off - - @Column(name = "ban_alarm_yn", nullable = false) - private Boolean banAlarmYn; // 제재 대상 알림 on off - - @Column(name = "change_info_yn", nullable = false) - private Boolean changeInfoYn; // 관심 팝업 정보 변경 알림 on off - -} diff --git a/src/main/java/com/kkokkomu/short_news/user/domain/User.java b/src/main/java/com/kkokkomu/short_news/user/domain/User.java index 1b46543..3d9e6bc 100644 --- a/src/main/java/com/kkokkomu/short_news/user/domain/User.java +++ b/src/main/java/com/kkokkomu/short_news/user/domain/User.java @@ -88,6 +88,9 @@ public class User { @Column(name = "alarm_ad_yn") private Boolean alarmAdYn; // 광고 알림 여부 + @Column(name = "alarm_banned_yn") + private Boolean alarmBannedYn; // 제재 대상 알림 on off + @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; // 생성 일자, 객체 생성 시 자동 설정 @@ -98,7 +101,7 @@ public class User { private List profileImgs; @Builder - public User(String email, String password, String nickname, LocalDate birthday, ESex sex, EUserRole role, ELoginProvider loginProvider, Boolean isLogin, String refreshToken, LocalDateTime bannedStartAt, LocalDateTime bannedEndAt, LocalDateTime deletedAt, Boolean isDeleted, Boolean privacyPolicyYn, Boolean serviceTermsYn, Boolean alarmYn, Boolean alarmNewContentYn, Boolean alarmReplyYn, Boolean alarmAdYn) { + public User(String email, String password, String nickname, LocalDate birthday, ESex sex, EUserRole role, ELoginProvider loginProvider, Boolean isLogin, String refreshToken, LocalDateTime bannedStartAt, LocalDateTime bannedEndAt, LocalDateTime deletedAt, Boolean isDeleted, Boolean privacyPolicyYn, Boolean serviceTermsYn, Boolean alarmYn, Boolean alarmNewContentYn, Boolean alarmReplyYn,Boolean alarmBannedYn, Boolean alarmAdYn) { this.email = email; this.password = password; this.nickname = nickname; @@ -118,6 +121,7 @@ public User(String email, String password, String nickname, LocalDate birthday, this.alarmYn = alarmYn; this.alarmNewContentYn = alarmNewContentYn; this.alarmReplyYn = alarmReplyYn; + this.alarmBannedYn = alarmBannedYn; this.alarmAdYn = alarmAdYn; this.createdAt = LocalDateTime.now(); // 객체 생성 시 현재 시간으로 설정 this.editedAt = LocalDateTime.now(); // 초기값을 현재 시간으로 설정 @@ -143,6 +147,7 @@ public static User toGuestEntity(OAuth2UserInfo oAuth2UserInfo, String encodedPa .alarmYn(false) .alarmNewContentYn(false) .alarmReplyYn(false) + .alarmBannedYn(false) .alarmAdYn(false) .isDeleted(false) .build(); From d1fcf6ded09adc7fa713ba1f62287c080115ecee Mon Sep 17 00:00:00 2001 From: Gouyeon Chung Date: Wed, 23 Oct 2024 05:36:20 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[KKM-161]=20FEAT=20:=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EC=84=A4=EC=A0=95=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../short_news/alarm/domain/FCMToken.java | 30 +++--- .../alarm/dto/request/CreateTokenDto.java | 15 +++ .../alarm/dto/response/FCMTokenDto.java | 23 ++++ .../alarm/repository/FCMTokenRepository.java | 27 +++++ .../alarm/service/FCMTokenService.java | 102 ++++++++++++++++++ .../short_news/core/config/FCMConfig.java | 40 +++++++ .../core/scheduler/FCMTokenScheduler.java | 29 +++++ .../handler/CustomSignOutProcessHandler.java | 3 +- .../kkokkomu/short_news/user/domain/User.java | 7 +- 10 files changed, 254 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/dto/request/CreateTokenDto.java create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/dto/response/FCMTokenDto.java create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/repository/FCMTokenRepository.java create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/service/FCMTokenService.java create mode 100644 src/main/java/com/kkokkomu/short_news/core/config/FCMConfig.java create mode 100644 src/main/java/com/kkokkomu/short_news/core/scheduler/FCMTokenScheduler.java diff --git a/build.gradle b/build.gradle index 920461c..c530e09 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.data:spring-data-redis:3.0.1' + // Firebase sdk + implementation 'com.google.firebase:firebase-admin:7.1.0' } tasks.named('test') { diff --git a/src/main/java/com/kkokkomu/short_news/alarm/domain/FCMToken.java b/src/main/java/com/kkokkomu/short_news/alarm/domain/FCMToken.java index 9f14c47..f99fe68 100644 --- a/src/main/java/com/kkokkomu/short_news/alarm/domain/FCMToken.java +++ b/src/main/java/com/kkokkomu/short_news/alarm/domain/FCMToken.java @@ -11,9 +11,8 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @DynamicUpdate -@Table(name = "notification_token") +@Table(name = "fcm_token") public class FCMToken { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -25,36 +24,33 @@ public class FCMToken { @Column(name = "token", nullable = false, columnDefinition = "TINYTEXT") private String token; // 토큰, 최대 255자 - @Column(name = "modify_date", nullable = false) - private LocalDateTime mod_dtm; // 토큰 갱신일 - - @Column(name = "expired_date", nullable = false) - private LocalDateTime exp_dtm; // 토큰 만료일 + @Column(name = "edited_at", nullable = false) + private LocalDateTime editedAt; // 토큰 갱신일 - @Column(name = "device" , nullable = false) - private String device; // android or ios + @Column(name = "expired_at", nullable = false) + private LocalDateTime expiredAt; // 토큰 만료일 @Column(name = "device_id", nullable = false, unique = true) private String deviceId; @Builder - public FCMToken(String token, LocalDateTime mod_dtm , String device, String deviceId ){ + public FCMToken(User user, String token, String deviceId ){ + this.user = user; this.token = token; - this.mod_dtm = mod_dtm; - this.exp_dtm = mod_dtm.plusMonths(1); - this.device = device; + this.editedAt = LocalDateTime.now(); + this.expiredAt = LocalDateTime.now().plusMonths(1); this.deviceId = deviceId; } // 토큰 갱신 public void refreshToken() { - this.mod_dtm = LocalDateTime.now(); - this.exp_dtm = mod_dtm.plusMonths(1); // 1달 갱신 + this.editedAt = LocalDateTime.now(); + this.expiredAt = editedAt.plusMonths(1); // 1달 갱신 } // 토큰 재생성 - public void regenerateToken(FCMToken fcmToken) { - this.token = fcmToken.getToken(); + public void regenerateToken(String fcmToken) { + this.token = fcmToken; refreshToken(); } } diff --git a/src/main/java/com/kkokkomu/short_news/alarm/dto/request/CreateTokenDto.java b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/CreateTokenDto.java new file mode 100644 index 0000000..f84d18e --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/CreateTokenDto.java @@ -0,0 +1,15 @@ +package com.kkokkomu.short_news.alarm.dto.request; + +import jakarta.validation.constraints.NotNull; + +public record CreateTokenDto ( + @NotNull + String fcmToken, + + @NotNull + String device, + + @NotNull + String deviceId +) { +} diff --git a/src/main/java/com/kkokkomu/short_news/alarm/dto/response/FCMTokenDto.java b/src/main/java/com/kkokkomu/short_news/alarm/dto/response/FCMTokenDto.java new file mode 100644 index 0000000..cf06c02 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/dto/response/FCMTokenDto.java @@ -0,0 +1,23 @@ +package com.kkokkomu.short_news.alarm.dto.response; + +import com.kkokkomu.short_news.alarm.domain.FCMToken; +import lombok.Builder; + +@Builder +public record FCMTokenDto( + Long id, + String token, + String deviceId, + String editedAt, + String expiredAt +) { + public static FCMTokenDto of(FCMToken fcmToken) { + return FCMTokenDto.builder() + .id(fcmToken.getId()) + .token(fcmToken.getToken()) + .deviceId(fcmToken.getDeviceId()) + .editedAt(fcmToken.getEditedAt().toString()) + .expiredAt(fcmToken.getExpiredAt().toString()) + .build(); + } +} diff --git a/src/main/java/com/kkokkomu/short_news/alarm/repository/FCMTokenRepository.java b/src/main/java/com/kkokkomu/short_news/alarm/repository/FCMTokenRepository.java new file mode 100644 index 0000000..befa754 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/repository/FCMTokenRepository.java @@ -0,0 +1,27 @@ +package com.kkokkomu.short_news.alarm.repository; + +import com.kkokkomu.short_news.alarm.domain.FCMToken; +import com.kkokkomu.short_news.user.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository +public interface FCMTokenRepository extends JpaRepository { + + // 만기된 토큰들 조회 + @Query("SELECT token FROM FCMToken token WHERE token.expiredAt <= :now") + List findExpiredTokenList(LocalDateTime now); + + List findAllByDeviceId(String deviceId); + + FCMToken findByToken(String token); + + Optional findByDeviceIdAndToken(String deviceId, String token); + + void deleteByDeviceIdAndUser(String deviceId, User user); +} diff --git a/src/main/java/com/kkokkomu/short_news/alarm/service/FCMTokenService.java b/src/main/java/com/kkokkomu/short_news/alarm/service/FCMTokenService.java new file mode 100644 index 0000000..569ffab --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/service/FCMTokenService.java @@ -0,0 +1,102 @@ +package com.kkokkomu.short_news.alarm.service; + +import com.google.firebase.messaging.FirebaseMessagingException; +import com.kkokkomu.short_news.alarm.domain.FCMToken; +import com.kkokkomu.short_news.alarm.dto.request.CreateTokenDto; +import com.kkokkomu.short_news.alarm.dto.response.FCMTokenDto; +import com.kkokkomu.short_news.alarm.repository.FCMTokenRepository; +import com.kkokkomu.short_news.core.exception.CommonException; +import com.kkokkomu.short_news.core.exception.ErrorCode; +import com.kkokkomu.short_news.user.domain.User; +import com.kkokkomu.short_news.user.service.UserLookupService; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class FCMTokenService { + private final FCMTokenRepository fcmTokenRepository; + + private final UserLookupService userLookupService; + + /* FCM TOKEN 등록, 회원가입 시 사용하기 */ + /* 로그인 및 회원가입 시 + * */ + @Transactional + public FCMTokenDto applyFCMToken(CreateTokenDto createTokenDto, Long userId) { + log.info("Applying FCM token: {}", createTokenDto.fcmToken()); + + // 같은 디바이스에 등록된 다른 토큰 전부 삭제 + List duplicateDevices = fcmTokenRepository.findAllByDeviceId(createTokenDto.deviceId()); + deleteTokens(duplicateDevices); + + // FCM 토큰 저장 + FCMToken fcmToken = createFCMToken(userId, createTokenDto.deviceId(), createTokenDto.fcmToken()); + + return FCMTokenDto.of(fcmToken); + } + + // 토큰 update 필요 여부 검증 메서드, 앱진입, 로그인 시 사용 + public FCMTokenDto verifyFCMToken(Long userId, String deviceId, String fcmToken) { + log.info("verify token : {}", fcmToken); + + // 토큰이 없거나 다르면 생성 + Optional fcmTokenOptional = fcmTokenRepository.findByDeviceIdAndToken(deviceId, fcmToken); + + FCMToken token = null; + if (fcmTokenOptional.isEmpty()) { // 토큰이 비어있으면 새로 생성 + token = createFCMToken(userId, deviceId, fcmToken); + } else if(!fcmToken.equals(fcmTokenOptional.get().getToken())) { // 토큰이 있지만, 다르다면 재설정 + token = fcmTokenRepository.findByToken(fcmToken); + token.regenerateToken(fcmToken); + } + + return FCMTokenDto.of(token); + } + + // 토큰 삭제 + // 로그아웃 시 요청 + public String deleteUserToken(String deviceId, Long userId) { + User user = userLookupService.findUserById(userId); + + fcmTokenRepository.deleteByDeviceIdAndUser(deviceId, user); + + return "success"; + } + + private FCMToken createFCMToken(Long userId, String deviceId, String token) { + // 유저 조회 + User user = userLookupService.findUserById(userId); + + FCMToken fcmToken = FCMToken.builder() + .token(token) + .deviceId(deviceId) + .user(user) + .build(); + return fcmTokenRepository.save(fcmToken); + } + + private void deleteToken(FCMToken token) { + fcmTokenRepository.delete(token); + } + + private void deleteTokens(List tokens) { + fcmTokenRepository.deleteAll(tokens); + } + + public void deleteExpiredTokens() { + log.info("deleteExpiredTokens"); + List expiredTokenList = fcmTokenRepository.findExpiredTokenList(LocalDateTime.now()); + + deleteTokens(expiredTokenList); + } +} diff --git a/src/main/java/com/kkokkomu/short_news/core/config/FCMConfig.java b/src/main/java/com/kkokkomu/short_news/core/config/FCMConfig.java new file mode 100644 index 0000000..aefe958 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/core/config/FCMConfig.java @@ -0,0 +1,40 @@ +package com.kkokkomu.short_news.core.config; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.messaging.FirebaseMessaging; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +@Configuration +public class FCMConfig { + + @Bean + FirebaseMessaging firebaseMessaging() throws IOException { + ClassPathResource resource = new ClassPathResource("firebase/firebase_key.json"); + InputStream refreshToken = resource.getInputStream(); + FirebaseApp firebaseApp = null; + List firebaseAppList = FirebaseApp.getApps(); + if (firebaseAppList != null && !firebaseAppList.isEmpty()) { + for (FirebaseApp app : firebaseAppList) { + if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) { + firebaseApp = app; + } + } + } else { + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(refreshToken)) + .build(); + + firebaseApp = FirebaseApp.initializeApp(options); + + } + return FirebaseMessaging.getInstance(firebaseApp); + } +} \ No newline at end of file diff --git a/src/main/java/com/kkokkomu/short_news/core/scheduler/FCMTokenScheduler.java b/src/main/java/com/kkokkomu/short_news/core/scheduler/FCMTokenScheduler.java new file mode 100644 index 0000000..227139c --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/core/scheduler/FCMTokenScheduler.java @@ -0,0 +1,29 @@ +package com.kkokkomu.short_news.core.scheduler; + +import com.google.firebase.messaging.FirebaseMessagingException; +import com.kkokkomu.short_news.alarm.domain.FCMToken; +import com.kkokkomu.short_news.alarm.service.FCMTokenService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; + +@Component +@Slf4j +@RequiredArgsConstructor +public class FCMTokenScheduler { + private final FCMTokenService fcmTokenService; + + // 만기 토큰 삭제 스케줄러 + @Scheduled(cron = "0 0 0 * * *") + private void deleteFCMToken() throws FirebaseMessagingException { + log.info("token manage scheduler start"); + + fcmTokenService.deleteExpiredTokens(); // 만기 토큰 삭제 + } +} diff --git a/src/main/java/com/kkokkomu/short_news/core/security/handler/CustomSignOutProcessHandler.java b/src/main/java/com/kkokkomu/short_news/core/security/handler/CustomSignOutProcessHandler.java index b6be53f..9a46ce3 100644 --- a/src/main/java/com/kkokkomu/short_news/core/security/handler/CustomSignOutProcessHandler.java +++ b/src/main/java/com/kkokkomu/short_news/core/security/handler/CustomSignOutProcessHandler.java @@ -17,6 +17,7 @@ public class CustomSignOutProcessHandler implements LogoutHandler { @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); - userRepository.updateRefreshTokenAndLoginStatus(userDetails.getId(), null, false); + userRepository.updateRefreshTokenAndLoginStatus(userDetails.getId(), null, false); // 리프레시 토큰 무료화 + } } diff --git a/src/main/java/com/kkokkomu/short_news/user/domain/User.java b/src/main/java/com/kkokkomu/short_news/user/domain/User.java index 3d9e6bc..fcee390 100644 --- a/src/main/java/com/kkokkomu/short_news/user/domain/User.java +++ b/src/main/java/com/kkokkomu/short_news/user/domain/User.java @@ -85,9 +85,6 @@ public class User { @Column(name = "alarm_reply_yn") private Boolean alarmReplyYn; // 대댓글 알림 여부 - @Column(name = "alarm_ad_yn") - private Boolean alarmAdYn; // 광고 알림 여부 - @Column(name = "alarm_banned_yn") private Boolean alarmBannedYn; // 제재 대상 알림 on off @@ -101,7 +98,7 @@ public class User { private List profileImgs; @Builder - public User(String email, String password, String nickname, LocalDate birthday, ESex sex, EUserRole role, ELoginProvider loginProvider, Boolean isLogin, String refreshToken, LocalDateTime bannedStartAt, LocalDateTime bannedEndAt, LocalDateTime deletedAt, Boolean isDeleted, Boolean privacyPolicyYn, Boolean serviceTermsYn, Boolean alarmYn, Boolean alarmNewContentYn, Boolean alarmReplyYn,Boolean alarmBannedYn, Boolean alarmAdYn) { + public User(String email, String password, String nickname, LocalDate birthday, ESex sex, EUserRole role, ELoginProvider loginProvider, Boolean isLogin, String refreshToken, LocalDateTime bannedStartAt, LocalDateTime bannedEndAt, LocalDateTime deletedAt, Boolean isDeleted, Boolean privacyPolicyYn, Boolean serviceTermsYn, Boolean alarmYn, Boolean alarmNewContentYn, Boolean alarmReplyYn,Boolean alarmBannedYn) { this.email = email; this.password = password; this.nickname = nickname; @@ -122,7 +119,6 @@ public User(String email, String password, String nickname, LocalDate birthday, this.alarmNewContentYn = alarmNewContentYn; this.alarmReplyYn = alarmReplyYn; this.alarmBannedYn = alarmBannedYn; - this.alarmAdYn = alarmAdYn; this.createdAt = LocalDateTime.now(); // 객체 생성 시 현재 시간으로 설정 this.editedAt = LocalDateTime.now(); // 초기값을 현재 시간으로 설정 } @@ -148,7 +144,6 @@ public static User toGuestEntity(OAuth2UserInfo oAuth2UserInfo, String encodedPa .alarmNewContentYn(false) .alarmReplyYn(false) .alarmBannedYn(false) - .alarmAdYn(false) .isDeleted(false) .build(); } From 9d7c328d9809da1c29abe29bb92dc50240bddc68 Mon Sep 17 00:00:00 2001 From: Gouyeon Chung Date: Wed, 23 Oct 2024 05:47:13 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[KKM-161]=20FEAT=20:=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/dto/request/CreateTokenDto.java | 3 --- .../user/controller/AuthController.java | 6 +++-- .../request/SocialRegisterRequestDto.java | 8 ++++++- .../short_news/user/service/AuthService.java | 22 ++++++++++++++----- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/kkokkomu/short_news/alarm/dto/request/CreateTokenDto.java b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/CreateTokenDto.java index f84d18e..272895e 100644 --- a/src/main/java/com/kkokkomu/short_news/alarm/dto/request/CreateTokenDto.java +++ b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/CreateTokenDto.java @@ -6,9 +6,6 @@ public record CreateTokenDto ( @NotNull String fcmToken, - @NotNull - String device, - @NotNull String deviceId ) { diff --git a/src/main/java/com/kkokkomu/short_news/user/controller/AuthController.java b/src/main/java/com/kkokkomu/short_news/user/controller/AuthController.java index 4309ac2..8683406 100644 --- a/src/main/java/com/kkokkomu/short_news/user/controller/AuthController.java +++ b/src/main/java/com/kkokkomu/short_news/user/controller/AuthController.java @@ -1,5 +1,6 @@ package com.kkokkomu.short_news.user.controller; +import com.kkokkomu.short_news.alarm.dto.request.CreateTokenDto; import com.kkokkomu.short_news.core.annotation.UserId; import com.kkokkomu.short_news.core.constant.Constant; import com.kkokkomu.short_news.user.dto.auth.request.SocialRegisterRequestDto; @@ -45,9 +46,10 @@ public ResponseDto socialRegister(@NotNull @RequestHeader(Constant. }) @PostMapping("/login/{provider}") public ResponseDto authSocialLogin(@PathVariable String provider, - @NotNull @RequestHeader(Constant.AUTHORIZATION_HEADER) String accessToken) { + @NotNull @RequestHeader(Constant.AUTHORIZATION_HEADER) String accessToken, + @RequestBody @Valid CreateTokenDto createTokenDto) { log.info("accessToken : " + accessToken); - return ResponseDto.ok(authService.authSocialLogin(accessToken, provider)); + return ResponseDto.ok(authService.authSocialLogin(accessToken, provider, createTokenDto)); } // @Operation(summary = "관리자 로그인", description = "회원가입 필요시 access token만 반환, 로그인 완료시 access, refresh 둘 다 반환") diff --git a/src/main/java/com/kkokkomu/short_news/user/dto/auth/request/SocialRegisterRequestDto.java b/src/main/java/com/kkokkomu/short_news/user/dto/auth/request/SocialRegisterRequestDto.java index 5145d83..8270340 100644 --- a/src/main/java/com/kkokkomu/short_news/user/dto/auth/request/SocialRegisterRequestDto.java +++ b/src/main/java/com/kkokkomu/short_news/user/dto/auth/request/SocialRegisterRequestDto.java @@ -20,6 +20,12 @@ public record SocialRegisterRequestDto( @NotNull LocalDate birthday, - String recommendCode + String recommendCode, + + @NotNull + String fcmToken, + + @NotNull + String deviceId ) { } diff --git a/src/main/java/com/kkokkomu/short_news/user/service/AuthService.java b/src/main/java/com/kkokkomu/short_news/user/service/AuthService.java index 3e593f1..8f22280 100644 --- a/src/main/java/com/kkokkomu/short_news/user/service/AuthService.java +++ b/src/main/java/com/kkokkomu/short_news/user/service/AuthService.java @@ -1,5 +1,7 @@ package com.kkokkomu.short_news.user.service; +import com.kkokkomu.short_news.alarm.dto.request.CreateTokenDto; +import com.kkokkomu.short_news.alarm.service.FCMTokenService; import com.kkokkomu.short_news.core.constant.Constant; import com.kkokkomu.short_news.core.oauth2.apple.AppleOAuthService; import com.kkokkomu.short_news.event.domain.ShareEvent; @@ -51,6 +53,7 @@ public class AuthService { private final BCryptPasswordEncoder bCryptPasswordEncoder; private final AppleOAuthService appleOAuthService; + private final FCMTokenService fcmTokenService; @Transactional public JwtTokenDto socialRegister(String accessToken, SocialRegisterRequestDto socialRegisterRequestDto) { // 소셜 로그인 후 회원 등록 및 토큰 발급 @@ -115,6 +118,9 @@ public JwtTokenDto socialRegister(String accessToken, SocialRegisterRequestDto s final JwtTokenDto jwtTokenDto = jwtUtil.generateToken(user.getId(), user.getRole()); user.updateRefreshToken(jwtTokenDto.refreshToken()); + // fcm 토큰 생성 + fcmTokenService.verifyFCMToken(userId, socialRegisterRequestDto.deviceId(), socialRegisterRequestDto.fcmToken()); + return jwtTokenDto; } @@ -132,12 +138,12 @@ public JwtTokenDto refresh(String refreshToken) { return jwtToken; } - public Object authSocialLogin(String token, String provider) { + public Object authSocialLogin(String token, String provider, CreateTokenDto createTokenDto) { String accessToken = refineToken(token); String loginProvider = provider.toUpperCase(); log.info("loginProvider : " + loginProvider); OAuth2UserInfo oAuth2UserInfoDto = getOAuth2UserInfo(loginProvider, accessToken); - return processUserLogin(oAuth2UserInfoDto, ELoginProvider.valueOf(loginProvider)); + return processUserLogin(oAuth2UserInfoDto, ELoginProvider.valueOf(loginProvider), createTokenDto); } // public Object adminSocialLogin(String accessToken, String provider) { @@ -167,7 +173,7 @@ public Object authSocialLogin(String token, String provider) { // return jwtToken; // } - private Object processUserLogin(OAuth2UserInfo oAuth2UserInfo, ELoginProvider provider) { + private Object processUserLogin(OAuth2UserInfo oAuth2UserInfo, ELoginProvider provider, CreateTokenDto createTokenDto) { Optional user = userRepository.findByEmailAndRole(oAuth2UserInfo.email(), EUserRole.USER); // 회원 탈퇴 여부 확인 if (user.isPresent() && user.get().getIsDeleted()) { @@ -179,8 +185,14 @@ private Object processUserLogin(OAuth2UserInfo oAuth2UserInfo, ELoginProvider pr } // USER 권한 + 이메일 정보가 DB에 존재 -> 팝핀 토큰 발급 및 로그인 상태 변경 if (user.isPresent() && user.get().getLoginProvider().equals(provider)) { - JwtTokenDto jwtTokenDto = jwtUtil.generateToken(user.get().getId(), EUserRole.USER); - userRepository.updateRefreshTokenAndLoginStatus(user.get().getId(), jwtTokenDto.refreshToken(), true); + Long userId = user.get().getId(); + + JwtTokenDto jwtTokenDto = jwtUtil.generateToken(userId, EUserRole.USER); + userRepository.updateRefreshTokenAndLoginStatus(userId, jwtTokenDto.refreshToken(), true); + + //fcm 토큰 검증 + fcmTokenService.verifyFCMToken(userId, createTokenDto.deviceId(), createTokenDto.fcmToken()); + return jwtTokenDto; } else { // 비밀번호 랜덤 생성 후 암호화해서 DB에 저장 From 44070a8d257800a8205a91857355cbbac5347a9b Mon Sep 17 00:00:00 2001 From: Gouyeon Chung Date: Wed, 23 Oct 2024 21:32:40 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[KKM-161]=20FEAT=20:=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/CustomSignOutProcessHandler.java | 33 +++++++++++++++++ .../controller/AlarmSettingController.java | 36 +++++++++++++++++++ .../user/controller/AuthController.java | 8 +++-- .../user/controller/UserController.java | 1 + .../kkokkomu/short_news/user/domain/User.java | 19 +++++++--- .../user/request/UpdateAlarmSettingDto.java | 11 ++++++ .../user/dto/user/response/UserDto.java | 11 +++++- .../user/service/AlarmSettingService.java | 32 +++++++++++++++++ .../short_news/user/service/AuthService.java | 6 +++- 9 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/kkokkomu/short_news/user/controller/AlarmSettingController.java create mode 100644 src/main/java/com/kkokkomu/short_news/user/dto/user/request/UpdateAlarmSettingDto.java create mode 100644 src/main/java/com/kkokkomu/short_news/user/service/AlarmSettingService.java diff --git a/src/main/java/com/kkokkomu/short_news/core/security/handler/CustomSignOutProcessHandler.java b/src/main/java/com/kkokkomu/short_news/core/security/handler/CustomSignOutProcessHandler.java index 9a46ce3..bddcf83 100644 --- a/src/main/java/com/kkokkomu/short_news/core/security/handler/CustomSignOutProcessHandler.java +++ b/src/main/java/com/kkokkomu/short_news/core/security/handler/CustomSignOutProcessHandler.java @@ -1,5 +1,7 @@ package com.kkokkomu.short_news.core.security.handler; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.kkokkomu.short_news.alarm.service.FCMTokenService; import com.kkokkomu.short_news.user.repository.UserRepository; import com.kkokkomu.short_news.core.security.info.CustomUserDetails; import jakarta.servlet.http.HttpServletRequest; @@ -9,15 +11,46 @@ import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.stereotype.Component; +import java.io.IOException; +import java.util.Map; + @RequiredArgsConstructor @Component public class CustomSignOutProcessHandler implements LogoutHandler { private final UserRepository userRepository; + private final FCMTokenService fcmTokenService; + + private final ObjectMapper objectMapper = new ObjectMapper(); + @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); userRepository.updateRefreshTokenAndLoginStatus(userDetails.getId(), null, false); // 리프레시 토큰 무료화 + removeToken(request, userDetails.getId()); // fcm 토큰 삭제 + } + + public void removeToken(HttpServletRequest request, Long userId) { + try { + // 요청의 Body를 Map으로 파싱 + Map body = objectMapper.readValue(request.getInputStream(), Map.class); + + String deviceId = body.get("deviceId"); + + if (userId != null) { + System.out.println("로그아웃 처리 중: userId = " + userId); + // 추가 처리 로직: 유저 세션 무효화 등 + } + if (deviceId != null) { + System.out.println("로그아웃 처리 중: token = " + deviceId); + // 예: 토큰 블랙리스트 처리 등 + } + + fcmTokenService.deleteUserToken(deviceId, userId); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("로그아웃 처리 중 오류 발생"); + } } } diff --git a/src/main/java/com/kkokkomu/short_news/user/controller/AlarmSettingController.java b/src/main/java/com/kkokkomu/short_news/user/controller/AlarmSettingController.java new file mode 100644 index 0000000..e188e5e --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/user/controller/AlarmSettingController.java @@ -0,0 +1,36 @@ +package com.kkokkomu.short_news.user.controller; + +import com.kkokkomu.short_news.core.annotation.UserId; +import com.kkokkomu.short_news.core.dto.ResponseDto; +import com.kkokkomu.short_news.user.dto.user.request.UpdateAlarmSettingDto; +import com.kkokkomu.short_news.user.dto.user.response.UserDto; +import com.kkokkomu.short_news.user.service.AlarmSettingService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "알람 설정") +@RestController +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/user/alarmSetting") +public class AlarmSettingController { + private final AlarmSettingService alarmSettingService; + + @Operation(summary = "알람 세팅") + @PutMapping("") + public ResponseDto alarmSetting( + @Parameter(hidden = true) @UserId Long userId, + @RequestBody @Valid UpdateAlarmSettingDto updateAlarmSettingDto + ) { + log.info("alarmSetting controller userId = {}", userId); + return ResponseDto.ok(alarmSettingService.updateAlarmSetting(updateAlarmSettingDto, userId)); + } +} diff --git a/src/main/java/com/kkokkomu/short_news/user/controller/AuthController.java b/src/main/java/com/kkokkomu/short_news/user/controller/AuthController.java index 8683406..8166779 100644 --- a/src/main/java/com/kkokkomu/short_news/user/controller/AuthController.java +++ b/src/main/java/com/kkokkomu/short_news/user/controller/AuthController.java @@ -8,6 +8,7 @@ import com.kkokkomu.short_news.core.dto.ResponseDto; import com.kkokkomu.short_news.user.service.AuthService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -72,12 +73,13 @@ public ResponseDto authSocialLogin(@PathVariable String provider, @Operation(summary = "토큰 재발급") @PostMapping("/refresh") public ResponseDto refresh( - @NotNull @RequestHeader(Constant.AUTHORIZATION_HEADER) String refreshToken){ - return ResponseDto.ok(authService.refresh(refreshToken)); + @NotNull @RequestHeader(Constant.AUTHORIZATION_HEADER) String refreshToken, + @RequestBody CreateTokenDto createTokenDto){ + return ResponseDto.ok(authService.refresh(refreshToken, createTokenDto)); } // swagger 표기용 - @Operation(summary = "로그아웃") + @Operation(summary = "로그아웃", description = "토큰 삭제 및 deviceId 전송 필요") @PostMapping("/logout") public ResponseDto logout() { diff --git a/src/main/java/com/kkokkomu/short_news/user/controller/UserController.java b/src/main/java/com/kkokkomu/short_news/user/controller/UserController.java index 3b9ab44..529b89f 100644 --- a/src/main/java/com/kkokkomu/short_news/user/controller/UserController.java +++ b/src/main/java/com/kkokkomu/short_news/user/controller/UserController.java @@ -2,6 +2,7 @@ import com.kkokkomu.short_news.core.annotation.UserId; import com.kkokkomu.short_news.core.dto.ResponseDto; +import com.kkokkomu.short_news.user.dto.user.request.UpdateAlarmSettingDto; import com.kkokkomu.short_news.user.dto.user.request.UpdateUserDto; import com.kkokkomu.short_news.user.dto.user.response.MyPageDto; import com.kkokkomu.short_news.user.dto.user.response.UserDto; diff --git a/src/main/java/com/kkokkomu/short_news/user/domain/User.java b/src/main/java/com/kkokkomu/short_news/user/domain/User.java index fcee390..1a5642e 100644 --- a/src/main/java/com/kkokkomu/short_news/user/domain/User.java +++ b/src/main/java/com/kkokkomu/short_news/user/domain/User.java @@ -6,6 +6,7 @@ import com.kkokkomu.short_news.core.type.ESex; import com.kkokkomu.short_news.core.type.EUserRole; import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; import lombok.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,8 +77,8 @@ public class User { @Column(name = "service_terms_yn") private Boolean serviceTermsYn; // 이용 약관 동의 여부 - @Column(name = "alarm_yn") - private Boolean alarmYn; // 푸시알림 여부 + @Column(name = "night_alarm_yn") + private Boolean nightAlarmYn; // 새 뉴스 알림 여부 @Column(name = "alarm_new_content_yn") private Boolean alarmNewContentYn; // 새 뉴스 알림 여부 @@ -98,7 +99,7 @@ public class User { private List profileImgs; @Builder - public User(String email, String password, String nickname, LocalDate birthday, ESex sex, EUserRole role, ELoginProvider loginProvider, Boolean isLogin, String refreshToken, LocalDateTime bannedStartAt, LocalDateTime bannedEndAt, LocalDateTime deletedAt, Boolean isDeleted, Boolean privacyPolicyYn, Boolean serviceTermsYn, Boolean alarmYn, Boolean alarmNewContentYn, Boolean alarmReplyYn,Boolean alarmBannedYn) { + public User(String email, String password, String nickname, LocalDate birthday, ESex sex, EUserRole role, ELoginProvider loginProvider, Boolean isLogin, String refreshToken, LocalDateTime bannedStartAt, LocalDateTime bannedEndAt, LocalDateTime deletedAt, Boolean isDeleted, Boolean privacyPolicyYn, Boolean serviceTermsYn, Boolean nightAlarmYn, Boolean alarmNewContentYn, Boolean alarmReplyYn,Boolean alarmBannedYn) { this.email = email; this.password = password; this.nickname = nickname; @@ -115,7 +116,7 @@ public User(String email, String password, String nickname, LocalDate birthday, this.isDeleted = false; this.privacyPolicyYn = privacyPolicyYn; this.serviceTermsYn = serviceTermsYn; - this.alarmYn = alarmYn; + this.nightAlarmYn = nightAlarmYn; this.alarmNewContentYn = alarmNewContentYn; this.alarmReplyYn = alarmReplyYn; this.alarmBannedYn = alarmBannedYn; @@ -140,7 +141,7 @@ public static User toGuestEntity(OAuth2UserInfo oAuth2UserInfo, String encodedPa .role(EUserRole.GUEST) .privacyPolicyYn(true) .serviceTermsYn(true) - .alarmYn(false) + .nightAlarmYn(false) .alarmNewContentYn(false) .alarmReplyYn(false) .alarmBannedYn(false) @@ -214,4 +215,12 @@ public void hardDelete() { this.birthday = null; this.sex = null; } + + // 알람 세팅 + public void updateAlarmSetting(Boolean nightAlarmYn, Boolean alarmNewContentYn, Boolean alarmReplyYn, Boolean alarmBannedYn) { + this.nightAlarmYn = nightAlarmYn; + this.alarmNewContentYn = alarmNewContentYn; + this.alarmReplyYn = alarmReplyYn; + this.alarmBannedYn = alarmBannedYn; + } } diff --git a/src/main/java/com/kkokkomu/short_news/user/dto/user/request/UpdateAlarmSettingDto.java b/src/main/java/com/kkokkomu/short_news/user/dto/user/request/UpdateAlarmSettingDto.java new file mode 100644 index 0000000..1edd179 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/user/dto/user/request/UpdateAlarmSettingDto.java @@ -0,0 +1,11 @@ +package com.kkokkomu.short_news.user.dto.user.request; + +import jakarta.validation.constraints.NotNull; + +public record UpdateAlarmSettingDto( + @NotNull Boolean nightAlarmYn, + @NotNull Boolean alarmNewContentYn, + @NotNull Boolean alarmReplyYn, + @NotNull Boolean alarmBannedYn +) { +} diff --git a/src/main/java/com/kkokkomu/short_news/user/dto/user/response/UserDto.java b/src/main/java/com/kkokkomu/short_news/user/dto/user/response/UserDto.java index 7a8a1d1..748fa5d 100644 --- a/src/main/java/com/kkokkomu/short_news/user/dto/user/response/UserDto.java +++ b/src/main/java/com/kkokkomu/short_news/user/dto/user/response/UserDto.java @@ -1,6 +1,7 @@ package com.kkokkomu.short_news.user.dto.user.response; import com.kkokkomu.short_news.user.domain.User; +import jakarta.validation.constraints.NotNull; import lombok.Builder; @Builder @@ -13,7 +14,11 @@ public record UserDto( String birthday, String createdAt, String editedAt, - String profileEditedAt + String profileEditedAt, + Boolean nightAlarmYn, + Boolean alarmNewContentYn, + Boolean alarmReplyYn, + Boolean alarmBannedYn ) { public static UserDto of(User user) { return UserDto.builder() @@ -26,6 +31,10 @@ public static UserDto of(User user) { .createdAt(user.getCreatedAt().toString()) .editedAt(user.getEditedAt().toString()) .profileEditedAt(user.getProfileImgs().get(0).getEditedAt().toString()) + .nightAlarmYn(user.getNightAlarmYn()) + .alarmNewContentYn(user.getAlarmNewContentYn()) + .alarmReplyYn(user.getAlarmReplyYn()) + .alarmBannedYn(user.getAlarmBannedYn()) .build(); } } diff --git a/src/main/java/com/kkokkomu/short_news/user/service/AlarmSettingService.java b/src/main/java/com/kkokkomu/short_news/user/service/AlarmSettingService.java new file mode 100644 index 0000000..69c5221 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/user/service/AlarmSettingService.java @@ -0,0 +1,32 @@ +package com.kkokkomu.short_news.user.service; + +import com.kkokkomu.short_news.user.domain.User; +import com.kkokkomu.short_news.user.dto.user.request.UpdateAlarmSettingDto; +import com.kkokkomu.short_news.user.dto.user.response.UserDto; +import com.kkokkomu.short_news.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class AlarmSettingService { + private final UserRepository userRepository; + + private final UserLookupService userLookupService; + + public UserDto updateAlarmSetting(UpdateAlarmSettingDto updateAlarmSettingDto, Long userId) { + User user = userLookupService.findUserById(userId); + + user.updateAlarmSetting( + updateAlarmSettingDto.nightAlarmYn(), + user.getAlarmNewContentYn(), + updateAlarmSettingDto.alarmReplyYn(), + updateAlarmSettingDto.alarmBannedYn()); + + userRepository.save(user); + + return UserDto.of(user); + } +} diff --git a/src/main/java/com/kkokkomu/short_news/user/service/AuthService.java b/src/main/java/com/kkokkomu/short_news/user/service/AuthService.java index 8f22280..0816c5d 100644 --- a/src/main/java/com/kkokkomu/short_news/user/service/AuthService.java +++ b/src/main/java/com/kkokkomu/short_news/user/service/AuthService.java @@ -125,7 +125,7 @@ public JwtTokenDto socialRegister(String accessToken, SocialRegisterRequestDto s } @Transactional - public JwtTokenDto refresh(String refreshToken) { + public JwtTokenDto refresh(String refreshToken, CreateTokenDto createTokenDto) { String token = refineToken(refreshToken); Long userId = jwtUtil.getUserIdFromToken(token); User user = userRepository.findById(userId) @@ -135,6 +135,10 @@ public JwtTokenDto refresh(String refreshToken) { } JwtTokenDto jwtToken = jwtUtil.generateToken(userId, user.getRole()); user.updateRefreshToken(jwtToken.refreshToken()); + + // fcm 토큰 업데이트 + fcmTokenService.verifyFCMToken(userId, createTokenDto.deviceId(), createTokenDto.fcmToken()); + return jwtToken; } From 08531be1dce85b955f093a91c2ab318cc4715b2b Mon Sep 17 00:00:00 2001 From: Gouyeon Chung Date: Thu, 24 Oct 2024 01:52:19 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[KKM-161]=20FEAT=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=95=8C=EB=9E=8C=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../alarm/controller/AlarmController.java | 36 ++++++ .../alarm/dto/request/FcmMessageDto.java | 29 +++++ .../alarm/dto/request/FcmSendDto.java | 21 ++++ .../alarm/dto/request/PushAlarmDto.java | 10 ++ .../alarm/repository/FCMTokenRepository.java | 2 + .../alarm/service/FCMSendService.java | 117 ++++++++++++++++++ .../alarm/service/FCMTokenService.java | 5 - .../short_news/core/config/FCMConfig.java | 40 ------ 9 files changed, 216 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/controller/AlarmController.java create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/dto/request/FcmMessageDto.java create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/dto/request/FcmSendDto.java create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/dto/request/PushAlarmDto.java create mode 100644 src/main/java/com/kkokkomu/short_news/alarm/service/FCMSendService.java delete mode 100644 src/main/java/com/kkokkomu/short_news/core/config/FCMConfig.java diff --git a/build.gradle b/build.gradle index c530e09..065609e 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,7 @@ dependencies { implementation 'org.springframework.data:spring-data-redis:3.0.1' // Firebase sdk - implementation 'com.google.firebase:firebase-admin:7.1.0' + implementation 'com.google.firebase:firebase-admin:9.2.0' } tasks.named('test') { diff --git a/src/main/java/com/kkokkomu/short_news/alarm/controller/AlarmController.java b/src/main/java/com/kkokkomu/short_news/alarm/controller/AlarmController.java new file mode 100644 index 0000000..32920f2 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/controller/AlarmController.java @@ -0,0 +1,36 @@ +package com.kkokkomu.short_news.alarm.controller; + +import com.kkokkomu.short_news.alarm.dto.request.FcmSendDto; +import com.kkokkomu.short_news.alarm.dto.request.PushAlarmDto; +import com.kkokkomu.short_news.alarm.service.FCMSendService; +import com.kkokkomu.short_news.core.annotation.UserId; +import com.kkokkomu.short_news.core.dto.ResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; + +@Tag(name = "알람") +@RestController +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/alarm") +public class AlarmController { + private FCMSendService fcmSendService; + + @Operation(summary = "알람 테스트") + @GetMapping("/test") + public ResponseDto test( + @Parameter(hidden = true) @UserId Long userId, + @RequestBody PushAlarmDto pushAlarmDto + ) throws IOException { + return ResponseDto.ok(fcmSendService.test(pushAlarmDto, userId)); + } +} diff --git a/src/main/java/com/kkokkomu/short_news/alarm/dto/request/FcmMessageDto.java b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/FcmMessageDto.java new file mode 100644 index 0000000..83c329e --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/FcmMessageDto.java @@ -0,0 +1,29 @@ +package com.kkokkomu.short_news.alarm.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class FcmMessageDto { + private boolean validateOnly; + private FcmMessageDto.Message message; + + @Builder + @AllArgsConstructor + @Getter + public static class Message { + private FcmMessageDto.Notification notification; + private String token; + } + + @Builder + @AllArgsConstructor + @Getter + public static class Notification { + private String title; + private String body; + private String image; + } +} \ No newline at end of file diff --git a/src/main/java/com/kkokkomu/short_news/alarm/dto/request/FcmSendDto.java b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/FcmSendDto.java new file mode 100644 index 0000000..828cf12 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/FcmSendDto.java @@ -0,0 +1,21 @@ +package com.kkokkomu.short_news.alarm.dto.request; + +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class FcmSendDto { + private String token; + + private String title; + + private String body; + + @Builder(toBuilder = true) + public FcmSendDto(String token, String title, String body) { + this.token = token; + this.title = title; + this.body = body; + } +} \ No newline at end of file diff --git a/src/main/java/com/kkokkomu/short_news/alarm/dto/request/PushAlarmDto.java b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/PushAlarmDto.java new file mode 100644 index 0000000..e0d2885 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/dto/request/PushAlarmDto.java @@ -0,0 +1,10 @@ +package com.kkokkomu.short_news.alarm.dto.request; + +import jakarta.validation.constraints.NotNull; + +public record PushAlarmDto( + @NotNull String deviceId, + @NotNull String title, + @NotNull String body +) { +} diff --git a/src/main/java/com/kkokkomu/short_news/alarm/repository/FCMTokenRepository.java b/src/main/java/com/kkokkomu/short_news/alarm/repository/FCMTokenRepository.java index befa754..cd391c4 100644 --- a/src/main/java/com/kkokkomu/short_news/alarm/repository/FCMTokenRepository.java +++ b/src/main/java/com/kkokkomu/short_news/alarm/repository/FCMTokenRepository.java @@ -23,5 +23,7 @@ public interface FCMTokenRepository extends JpaRepository { Optional findByDeviceIdAndToken(String deviceId, String token); + FCMToken findByDeviceIdAndUserId(String deviceId, Long userId); + void deleteByDeviceIdAndUser(String deviceId, User user); } diff --git a/src/main/java/com/kkokkomu/short_news/alarm/service/FCMSendService.java b/src/main/java/com/kkokkomu/short_news/alarm/service/FCMSendService.java new file mode 100644 index 0000000..10063e1 --- /dev/null +++ b/src/main/java/com/kkokkomu/short_news/alarm/service/FCMSendService.java @@ -0,0 +1,117 @@ +package com.kkokkomu.short_news.alarm.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.auth.oauth2.GoogleCredentials; +import com.kkokkomu.short_news.alarm.domain.FCMToken; +import com.kkokkomu.short_news.alarm.dto.request.FcmMessageDto; +import com.kkokkomu.short_news.alarm.dto.request.FcmSendDto; +import com.kkokkomu.short_news.alarm.dto.request.PushAlarmDto; +import com.kkokkomu.short_news.alarm.repository.FCMTokenRepository; +import com.kkokkomu.short_news.user.domain.User; +import com.kkokkomu.short_news.user.service.UserLookupService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.*; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class FCMSendService { + @Value("${fcm.firebase-key}") + private String firebaseConfigPath; + + private final FCMTokenRepository fcmTokenRepository; + + private final UserLookupService userLookupService; + + public String test(PushAlarmDto pushAlarmDto, Long userId) throws IOException { + FCMToken fcmToken = fcmTokenRepository.findByDeviceIdAndUserId(pushAlarmDto.deviceId(), userId); + + sendMessageTo( + FcmSendDto.builder() + .token(fcmToken.getToken()) + .title(pushAlarmDto.title()) + .body(pushAlarmDto.body()) + .build() + ); + + return "success"; + } + + /** + * 푸시 메시지 처리를 수행하는 비즈니스 로직 + * + * @param fcmSendDto 모바일에서 전달받은 Object + * @return 성공(1), 실패(0) + */ + public int sendMessageTo(FcmSendDto fcmSendDto) throws IOException { + + String message = makeMessage(fcmSendDto); + RestTemplate restTemplate = new RestTemplate(); + /** + * 추가된 사항 : RestTemplate 이용중 클라이언트의 한글 깨짐 증상에 대한 수정 + * @refernece : https://stackoverflow.com/questions/29392422/how-can-i-tell-resttemplate-to-post-with-utf-8-encoding + */ + restTemplate.getMessageConverters() + .add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + getAccessToken()); + + HttpEntity entity = new HttpEntity<>(message, headers); + + String API_URL = ""; + ResponseEntity response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class); + + log.info(String.valueOf(response.getStatusCode())); + + return response.getStatusCode() == HttpStatus.OK ? 1 : 0; + } + + /** + * Firebase Admin SDK의 비공개 키를 참조하여 Bearer 토큰을 발급 받습니다. + * + * @return Bearer token + */ + private String getAccessToken() throws IOException { + GoogleCredentials googleCredentials = GoogleCredentials + .fromStream(new ClassPathResource(firebaseConfigPath).getInputStream()) + .createScoped(List.of("")); + + googleCredentials.refreshIfExpired(); + return googleCredentials.getAccessToken().getTokenValue(); + } + + /** + * FCM 전송 정보를 기반으로 메시지를 구성합니다. (Object -> String) + * + * @param fcmSendDto FcmSendDto + * @return String + */ + private String makeMessage(FcmSendDto fcmSendDto) throws JsonProcessingException { + + ObjectMapper om = new ObjectMapper(); + FcmMessageDto fcmMessageDto = FcmMessageDto.builder() + .message(FcmMessageDto.Message.builder() + .token(fcmSendDto.getToken()) + .notification(FcmMessageDto.Notification.builder() + .title(fcmSendDto.getTitle()) + .body(fcmSendDto.getBody()) + .image(null) + .build() + ).build()).validateOnly(false).build(); + + return om.writeValueAsString(fcmMessageDto); + } +} diff --git a/src/main/java/com/kkokkomu/short_news/alarm/service/FCMTokenService.java b/src/main/java/com/kkokkomu/short_news/alarm/service/FCMTokenService.java index 569ffab..a93d0eb 100644 --- a/src/main/java/com/kkokkomu/short_news/alarm/service/FCMTokenService.java +++ b/src/main/java/com/kkokkomu/short_news/alarm/service/FCMTokenService.java @@ -1,12 +1,9 @@ package com.kkokkomu.short_news.alarm.service; -import com.google.firebase.messaging.FirebaseMessagingException; import com.kkokkomu.short_news.alarm.domain.FCMToken; import com.kkokkomu.short_news.alarm.dto.request.CreateTokenDto; import com.kkokkomu.short_news.alarm.dto.response.FCMTokenDto; import com.kkokkomu.short_news.alarm.repository.FCMTokenRepository; -import com.kkokkomu.short_news.core.exception.CommonException; -import com.kkokkomu.short_news.core.exception.ErrorCode; import com.kkokkomu.short_news.user.domain.User; import com.kkokkomu.short_news.user.service.UserLookupService; import jakarta.transaction.Transactional; @@ -15,8 +12,6 @@ import org.springframework.stereotype.Service; import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/kkokkomu/short_news/core/config/FCMConfig.java b/src/main/java/com/kkokkomu/short_news/core/config/FCMConfig.java deleted file mode 100644 index aefe958..0000000 --- a/src/main/java/com/kkokkomu/short_news/core/config/FCMConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.kkokkomu.short_news.core.config; - -import com.google.auth.oauth2.GoogleCredentials; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; -import com.google.firebase.messaging.FirebaseMessaging; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; - -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -@Configuration -public class FCMConfig { - - @Bean - FirebaseMessaging firebaseMessaging() throws IOException { - ClassPathResource resource = new ClassPathResource("firebase/firebase_key.json"); - InputStream refreshToken = resource.getInputStream(); - FirebaseApp firebaseApp = null; - List firebaseAppList = FirebaseApp.getApps(); - if (firebaseAppList != null && !firebaseAppList.isEmpty()) { - for (FirebaseApp app : firebaseAppList) { - if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) { - firebaseApp = app; - } - } - } else { - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(GoogleCredentials.fromStream(refreshToken)) - .build(); - - firebaseApp = FirebaseApp.initializeApp(options); - - } - return FirebaseMessaging.getInstance(firebaseApp); - } -} \ No newline at end of file From 523645b0ab6628d0e9d035aa2fe6b1a0cf39948a Mon Sep 17 00:00:00 2001 From: Gouyeon Chung Date: Thu, 24 Oct 2024 01:59:25 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[KKM-161]=20FEAT=20:=20=EC=95=8C=EB=9E=8C?= =?UTF-8?q?=20=EC=A0=84=EC=86=A1=20exception=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/service/FCMSendService.java | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/kkokkomu/short_news/alarm/service/FCMSendService.java b/src/main/java/com/kkokkomu/short_news/alarm/service/FCMSendService.java index 10063e1..8a25df5 100644 --- a/src/main/java/com/kkokkomu/short_news/alarm/service/FCMSendService.java +++ b/src/main/java/com/kkokkomu/short_news/alarm/service/FCMSendService.java @@ -34,7 +34,7 @@ public class FCMSendService { private final UserLookupService userLookupService; - public String test(PushAlarmDto pushAlarmDto, Long userId) throws IOException { + public String test(PushAlarmDto pushAlarmDto, Long userId) { FCMToken fcmToken = fcmTokenRepository.findByDeviceIdAndUserId(pushAlarmDto.deviceId(), userId); sendMessageTo( @@ -54,29 +54,33 @@ public String test(PushAlarmDto pushAlarmDto, Long userId) throws IOException { * @param fcmSendDto 모바일에서 전달받은 Object * @return 성공(1), 실패(0) */ - public int sendMessageTo(FcmSendDto fcmSendDto) throws IOException { - - String message = makeMessage(fcmSendDto); - RestTemplate restTemplate = new RestTemplate(); - /** - * 추가된 사항 : RestTemplate 이용중 클라이언트의 한글 깨짐 증상에 대한 수정 - * @refernece : https://stackoverflow.com/questions/29392422/how-can-i-tell-resttemplate-to-post-with-utf-8-encoding - */ - restTemplate.getMessageConverters() - .add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.set("Authorization", "Bearer " + getAccessToken()); - - HttpEntity entity = new HttpEntity<>(message, headers); - - String API_URL = ""; - ResponseEntity response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class); - - log.info(String.valueOf(response.getStatusCode())); - - return response.getStatusCode() == HttpStatus.OK ? 1 : 0; + public int sendMessageTo(FcmSendDto fcmSendDto) { + try { + String message = makeMessage(fcmSendDto); + RestTemplate restTemplate = new RestTemplate(); + /** + * 추가된 사항 : RestTemplate 이용중 클라이언트의 한글 깨짐 증상에 대한 수정 + * @refernece : https://stackoverflow.com/questions/29392422/how-can-i-tell-resttemplate-to-post-with-utf-8-encoding + */ + restTemplate.getMessageConverters() + .add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + getAccessToken()); + + HttpEntity entity = new HttpEntity<>(message, headers); + + String API_URL = ""; + ResponseEntity response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class); + + log.info(String.valueOf(response.getStatusCode())); + + return response.getStatusCode() == HttpStatus.OK ? 1 : 0; + } catch (IOException e) { + e.printStackTrace(); + return 0; + } } /** From ce563b4d1b891da28c9de847be8faa306da2b18d Mon Sep 17 00:00:00 2001 From: Gouyeon Chung Date: Sun, 27 Oct 2024 03:58:08 +0900 Subject: [PATCH 7/8] =?UTF-8?q?FIX=20:=20=EB=89=B4=EC=8A=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=A0=95=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kkokkomu/short_news/core/util/CategoryUtil.java | 2 +- .../kkokkomu/short_news/news/service/AdminNewsService.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kkokkomu/short_news/core/util/CategoryUtil.java b/src/main/java/com/kkokkomu/short_news/core/util/CategoryUtil.java index 98bcdd7..4a888cd 100644 --- a/src/main/java/com/kkokkomu/short_news/core/util/CategoryUtil.java +++ b/src/main/java/com/kkokkomu/short_news/core/util/CategoryUtil.java @@ -14,7 +14,7 @@ public class CategoryUtil { public ECategory getCategoryByName(String categoryName) { ECategory category = null; - if (Objects.equals(categoryName, "정치") && Objects.equals(categoryName, "총선")) { + if (Objects.equals(categoryName, "정치") || Objects.equals(categoryName, "총선")) { category = ECategory.POLITICS; } else if (Objects.equals(categoryName, "사회")) { category = ECategory.SOCIAL; diff --git a/src/main/java/com/kkokkomu/short_news/news/service/AdminNewsService.java b/src/main/java/com/kkokkomu/short_news/news/service/AdminNewsService.java index 877e6f2..480910e 100644 --- a/src/main/java/com/kkokkomu/short_news/news/service/AdminNewsService.java +++ b/src/main/java/com/kkokkomu/short_news/news/service/AdminNewsService.java @@ -115,7 +115,7 @@ public List generateNewsList(CreateGenerateNewsDto createGenera log.info("news ranking rese"); News topNews = newsRepository.findTopByOrderByScoreDesc(); Double topScore = topNews.getScore() * -1; - Double reseScore = topScore - 10; + Double reseScore = topScore - 100; List newsListAll = newsRepository.findAll(); for (News news : newsListAll) { @@ -262,7 +262,7 @@ public List generateNews(CreateGenerateNewsDto createGenerateNe News topNews = newsRepository.findTopByOrderByScoreDesc(); log.info("top news {}", topNews.getId()); Double topScore = topNews.getScore() * -1; - Double reseScore = topScore - 10; + Double reseScore = topScore - 100; List newsListAll = newsRepository.findAll(); for (News news : newsListAll) { From 26d4cf877f01560d5948bf262ec318e22255aa87 Mon Sep 17 00:00:00 2001 From: Gouyeon Chung Date: Tue, 29 Oct 2024 04:59:24 +0900 Subject: [PATCH 8/8] =?UTF-8?q?FEAT=20:=20=EB=89=B4=EC=8A=A4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../news/controller/AdminNewsController.java | 10 +++++++--- .../java/com/kkokkomu/short_news/news/domain/News.java | 4 ++++ .../short_news/news/service/AdminNewsService.java | 8 ++++++++ .../report/controller/AdminReportedNewsController.java | 10 +++++----- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/kkokkomu/short_news/news/controller/AdminNewsController.java b/src/main/java/com/kkokkomu/short_news/news/controller/AdminNewsController.java index 6c22bb9..78b3dfa 100644 --- a/src/main/java/com/kkokkomu/short_news/news/controller/AdminNewsController.java +++ b/src/main/java/com/kkokkomu/short_news/news/controller/AdminNewsController.java @@ -14,7 +14,6 @@ import java.util.List; -@Tag(name = "관리자 뉴스") @RestController @Slf4j @RequiredArgsConstructor @@ -28,12 +27,17 @@ public ResponseDto> generateNews(@RequestBody CreateGenera return ResponseDto.ok(adminNewsService.generateNews(createGenerateNewsDto)); } - @Operation(summary = "뉴스 수정") @PostMapping("") public ResponseDto updateNews(@RequestBody UpdateNewsDto updateNewsDto) { log.info("updateNews controller"); return ResponseDto.ok(adminNewsService.updateNews(updateNewsDto)); - } + } // 뉴스 수정 + + @DeleteMapping("") + public ResponseDto deleteNews(@RequestParam(value = "newsId") Long newsId) { + log.info("updateNews controller"); + return ResponseDto.ok(adminNewsService.deleteNews(newsId)); + } // 뉴스 삭제 @PutMapping("/rank") public String syncGlobalRank() { diff --git a/src/main/java/com/kkokkomu/short_news/news/domain/News.java b/src/main/java/com/kkokkomu/short_news/news/domain/News.java index d2b121b..c76bc6f 100644 --- a/src/main/java/com/kkokkomu/short_news/news/domain/News.java +++ b/src/main/java/com/kkokkomu/short_news/news/domain/News.java @@ -3,6 +3,7 @@ import com.kkokkomu.short_news.core.type.ECategory; import com.kkokkomu.short_news.comment.domain.Comment; import com.kkokkomu.short_news.keyword.domain.NewsKeyword; +import com.kkokkomu.short_news.report.domain.ReportedNews; import jakarta.persistence.*; import lombok.*; import java.time.LocalDateTime; @@ -71,6 +72,9 @@ public class News { @OneToMany(mappedBy = "news", cascade = CascadeType.ALL, orphanRemoval = true) private List newsKeywords = new ArrayList<>();; // 뉴스 키워드 매핑 + @OneToMany(mappedBy = "news", cascade = CascadeType.ALL, orphanRemoval = true) + private List reportedNews = new ArrayList<>();; // 뉴스 신고 내역 매핑 + @Builder public News(String shortformUrl, String youtubeUrl, String instagramUrl, String relatedUrl, String thumbnail, String title, String summary, ECategory category) { this.shortformUrl = shortformUrl; diff --git a/src/main/java/com/kkokkomu/short_news/news/service/AdminNewsService.java b/src/main/java/com/kkokkomu/short_news/news/service/AdminNewsService.java index 480910e..429a6ce 100644 --- a/src/main/java/com/kkokkomu/short_news/news/service/AdminNewsService.java +++ b/src/main/java/com/kkokkomu/short_news/news/service/AdminNewsService.java @@ -381,6 +381,14 @@ public NewsDto updateNews(UpdateNewsDto updateNewsDto) { return NewsDto.of(news); } // 뉴스 수정 + public String deleteNews(Long newsId) { + News news = newsLookupService.findNewsById(newsId); + + newsRepository.delete(news); + + return "success"; + } // 뉴스 수정 + public void syncRanking() { log.info("syncRanking"); Set> scores = redisService.getAllGlobalRank(); diff --git a/src/main/java/com/kkokkomu/short_news/report/controller/AdminReportedNewsController.java b/src/main/java/com/kkokkomu/short_news/report/controller/AdminReportedNewsController.java index 99e112b..2c9718c 100644 --- a/src/main/java/com/kkokkomu/short_news/report/controller/AdminReportedNewsController.java +++ b/src/main/java/com/kkokkomu/short_news/report/controller/AdminReportedNewsController.java @@ -15,7 +15,7 @@ import java.util.List; -@Tag(name = "뉴스 신고(관리자)") +//@Tag(name = "뉴스 신고(관리자)") @RestController @Slf4j @RequiredArgsConstructor @@ -23,7 +23,7 @@ public class AdminReportedNewsController { private final ReportedNewsService reportedNewsService; - @Operation(summary = "관리자 뉴스 신고 리스트 조회") +// @Operation(summary = "관리자 뉴스 신고 리스트 조회") @GetMapping("/unexecuted") public ResponseDto>> readUnexecutedReportedNews(@RequestParam int size, @RequestParam(required = false) Long cursorId @@ -32,7 +32,7 @@ public ResponseDto>> readUnexecuted return ResponseDto.ok(reportedNewsService.findUnexecutedAdminReportedNews(cursorId, size)); } - @Operation(summary = "관리자 뉴스 신고 처리완료 리스트 조회") +// @Operation(summary = "관리자 뉴스 신고 처리완료 리스트 조회") @GetMapping("/executed") public ResponseDto>> readExecuedReportedNews(@RequestParam int size, @RequestParam(required = false) Long cursorId @@ -41,7 +41,7 @@ public ResponseDto>> readExecuedRep return ResponseDto.ok(reportedNewsService.findExecutedAdminReportedNews(cursorId, size)); } - @Operation(summary = "관리자 뉴스 신고 처리") +// @Operation(summary = "관리자 뉴스 신고 처리") @PostMapping("/execute") public ResponseDto executeReportedNews(@RequestBody ExecuteReportedNews executeReportedNews, @UserId Long adminId @@ -50,7 +50,7 @@ public ResponseDto executeReportedNews(@RequestBody Execut return ResponseDto.ok(reportedNewsService.executeReportedNews(executeReportedNews, adminId)); } - @Operation(summary = "관리자 뉴스 신고 기각 처리") +// @Operation(summary = "관리자 뉴스 신고 기각 처리") @PostMapping("/dismiss") public ResponseDto dismissReportedNews(@RequestBody ExecuteReportedNews executeReportedNews, @UserId Long adminId