From f9111587eeba02418274d4f9a3b54add887c614a Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Sun, 11 Aug 2024 23:49:32 +0900 Subject: [PATCH 1/9] =?UTF-8?q?Chore:=20Firebase=20SDK=20key=20file,=20.gi?= =?UTF-8?q?tignore=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c2065bc..07fc33f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +/src/main/resources/firebase/ +/src/main/resources/firebase/suppin-a5657-firebase-adminsdk-s75m9-d65cc88029.json ### STS ### .apt_generated From d0733738cdbbc1ccd2c2147082e960aed8a36378 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Sun, 11 Aug 2024 23:50:17 +0900 Subject: [PATCH 2/9] =?UTF-8?q?Chore:=20Firebase=20Admin=20SDK=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 7a4277a..6983469 100644 --- a/build.gradle +++ b/build.gradle @@ -31,20 +31,23 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + implementation 'com.fasterxml.jackson.core:jackson-core:2.16.1' + implementation 'org.springframework.boot:spring-boot-starter-mail' - // JWT dependencies + //JWT dependencies implementation 'io.jsonwebtoken:jjwt-api:0.12.2' implementation 'io.jsonwebtoken:jjwt-impl:0.12.2' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.2' - implementation 'org.springframework.boot:spring-boot-starter-mail' - //selenium implementation 'org.seleniumhq.selenium:selenium-java:4.1.4' implementation 'io.github.bonigarcia:webdrivermanager:5.0.3' implementation 'org.jsoup:jsoup:1.13.1' testImplementation 'org.seleniumhq.selenium:selenium-java:4.22.0' + //Google Firebase + implementation 'com.google.firebase:firebase-admin:9.2.0' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' From 46ffe724a36e71de4c0419c4fe870be2983f431d Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Sun, 11 Aug 2024 23:51:40 +0900 Subject: [PATCH 3/9] =?UTF-8?q?Feat:=20FCM=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cmc/suppin/fcm/config/FirebaseConfig.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/com/cmc/suppin/fcm/config/FirebaseConfig.java diff --git a/src/main/java/com/cmc/suppin/fcm/config/FirebaseConfig.java b/src/main/java/com/cmc/suppin/fcm/config/FirebaseConfig.java new file mode 100644 index 0000000..3853038 --- /dev/null +++ b/src/main/java/com/cmc/suppin/fcm/config/FirebaseConfig.java @@ -0,0 +1,29 @@ +package com.cmc.suppin.fcm.config; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +import javax.annotation.PostConstruct; +import java.io.InputStream; + +@Configuration +public class FirebaseConfig { + @PostConstruct + public void init() { + try { + InputStream serviceAccount = new ClassPathResource("suppin-a5657-firebase-adminsdk.json").getInputStream(); + FirebaseOptions options = new FirebaseOptions.Builder() + .setCredentials(GoogleCredentials.fromStream(serviceAccount)) + .build(); + + if (FirebaseApp.getApps().isEmpty()) { // FirebaseApp이 이미 초기화되어 있지 않은 경우에만 초기화 실행 + FirebaseApp.initializeApp(options); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} From a8936bfe2b515d34cb13a0c7ba292e0f6931a90c Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Sun, 11 Aug 2024 23:54:15 +0900 Subject: [PATCH 4/9] =?UTF-8?q?Feat:=20FCM=20=ED=91=B8=EC=8B=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20Firebase=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../suppin/fcm/controller/FcmController.java | 35 +++++++ .../fcm/controller/dto/FcmMessageDTO.java | 32 +++++++ .../suppin/fcm/controller/dto/FcmSendDTO.java | 24 +++++ .../cmc/suppin/fcm/service/FcmService.java | 12 +++ .../suppin/fcm/service/FcmServiceImpl.java | 96 +++++++++++++++++++ 5 files changed, 199 insertions(+) create mode 100644 src/main/java/com/cmc/suppin/fcm/controller/FcmController.java create mode 100644 src/main/java/com/cmc/suppin/fcm/controller/dto/FcmMessageDTO.java create mode 100644 src/main/java/com/cmc/suppin/fcm/controller/dto/FcmSendDTO.java create mode 100644 src/main/java/com/cmc/suppin/fcm/service/FcmService.java create mode 100644 src/main/java/com/cmc/suppin/fcm/service/FcmServiceImpl.java diff --git a/src/main/java/com/cmc/suppin/fcm/controller/FcmController.java b/src/main/java/com/cmc/suppin/fcm/controller/FcmController.java new file mode 100644 index 0000000..738d151 --- /dev/null +++ b/src/main/java/com/cmc/suppin/fcm/controller/FcmController.java @@ -0,0 +1,35 @@ +package com.cmc.suppin.fcm.controller; + +import com.cmc.suppin.fcm.controller.dto.FcmSendDTO; +import com.cmc.suppin.fcm.service.FcmService; +import com.cmc.suppin.global.response.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +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; + +@Slf4j +@RestController +@RequestMapping("/api/v1/fcm") +public class FcmController { + + private final FcmService fcmService; + + public FcmController(FcmService fcmService) { + this.fcmService = fcmService; + } + + // 모바일로부터 사용자 FCM 토큰, 메시지 제목, 내용을 받아서 서비스를 처리 + @PostMapping("/send") + public ResponseEntity> pushMessage(@RequestBody @Validated FcmSendDTO fcmSendDto) throws IOException { + log.debug("[+] 푸시 메시지를 전송합니다. "); + int result = fcmService.sendMessageTo(fcmSendDto); + + return ResponseEntity.ok(ApiResponse.of(result)); + } +} diff --git a/src/main/java/com/cmc/suppin/fcm/controller/dto/FcmMessageDTO.java b/src/main/java/com/cmc/suppin/fcm/controller/dto/FcmMessageDTO.java new file mode 100644 index 0000000..cd70c27 --- /dev/null +++ b/src/main/java/com/cmc/suppin/fcm/controller/dto/FcmMessageDTO.java @@ -0,0 +1,32 @@ +package com.cmc.suppin.fcm.controller.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +/** + * FCM에 실제 전송될 데이터의 DTO + */ +@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; + } +} diff --git a/src/main/java/com/cmc/suppin/fcm/controller/dto/FcmSendDTO.java b/src/main/java/com/cmc/suppin/fcm/controller/dto/FcmSendDTO.java new file mode 100644 index 0000000..3189d0f --- /dev/null +++ b/src/main/java/com/cmc/suppin/fcm/controller/dto/FcmSendDTO.java @@ -0,0 +1,24 @@ +package com.cmc.suppin.fcm.controller.dto; + +import lombok.*; + +/** + * 모바일에서 전달받은 객체를 FCM으로 전송하기 위한 DTO + */ +@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; + } +} diff --git a/src/main/java/com/cmc/suppin/fcm/service/FcmService.java b/src/main/java/com/cmc/suppin/fcm/service/FcmService.java new file mode 100644 index 0000000..8790906 --- /dev/null +++ b/src/main/java/com/cmc/suppin/fcm/service/FcmService.java @@ -0,0 +1,12 @@ +package com.cmc.suppin.fcm.service; + +import com.cmc.suppin.fcm.controller.dto.FcmSendDTO; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +public interface FcmService { + + int sendMessageTo(FcmSendDTO fcmSendDTO) throws IOException; +} diff --git a/src/main/java/com/cmc/suppin/fcm/service/FcmServiceImpl.java b/src/main/java/com/cmc/suppin/fcm/service/FcmServiceImpl.java new file mode 100644 index 0000000..0edde51 --- /dev/null +++ b/src/main/java/com/cmc/suppin/fcm/service/FcmServiceImpl.java @@ -0,0 +1,96 @@ +package com.cmc.suppin.fcm.service; + +import com.cmc.suppin.fcm.controller.dto.FcmMessageDTO; +import com.cmc.suppin.fcm.controller.dto.FcmSendDTO; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.auth.oauth2.GoogleCredentials; +import lombok.extern.slf4j.Slf4j; +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 +@Slf4j +public class FcmServiceImpl implements FcmService { + + /** + * 푸시 메시지 처리를 수행하는 비즈니스 로직 + * + * @param fcmSendDTO 모바일에서 전달받은 Object + * @return 성공(1), 실패(0) + */ + @Override + public int sendMessageTo(FcmSendDTO fcmSendDTO) throws IOException { + + try { + String message = makeMessage(fcmSendDTO); + RestTemplate restTemplate = new RestTemplate(); + + 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 = "https://fcm.googleapis.com/v1/projects/suppin-a5657/messages:send"; + ResponseEntity response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, String.class); + + if (response.getStatusCode() == HttpStatus.OK) { + return 1; + } else { + log.error("FCM 메시지 전송 실패: {}", response.getStatusCode()); + return 0; + } + } catch (Exception e) { + log.error("FCM 메시지 전송 중 예외 발생", e); + return 0; + } + } + + /** + * Firebase Admin SDK의 비공개 키를 참조하여 Bearer 토큰을 발급 받습니다. + * + * @return Bearer token, String + */ + private String getAccessToken() throws IOException { + String firebaseConfigPath = "firebase/suppin-a5657-firebase-adminsdk.json"; + + GoogleCredentials googleCredentials = GoogleCredentials + .fromStream(new ClassPathResource(firebaseConfigPath).getInputStream()) + .createScoped(List.of("")); + + googleCredentials.refreshIfExpired(); + return googleCredentials.getAccessToken().getTokenValue(); + } + + /** + * FCM 전송 정보를 기반으로 메시지를 구성합니다. (Object -> String) + * + * @param fcmSendDTO, 모바일에서 전달받은 Object + * @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); + } +} From fc9c083167b948e982258ac17eb1ec7c4ef40f7a Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 12 Aug 2024 00:34:20 +0900 Subject: [PATCH 5/9] =?UTF-8?q?Fix:=20crontab=20=EB=A7=A4=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EB=A7=88=EB=8B=A4=20=EC=8B=A4=ED=96=89=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/cmc/suppin/event/events/service/ScheduledTasks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cmc/suppin/event/events/service/ScheduledTasks.java b/src/main/java/com/cmc/suppin/event/events/service/ScheduledTasks.java index f7f5ca2..7e2b0eb 100644 --- a/src/main/java/com/cmc/suppin/event/events/service/ScheduledTasks.java +++ b/src/main/java/com/cmc/suppin/event/events/service/ScheduledTasks.java @@ -10,7 +10,7 @@ public class ScheduledTasks { private final EventService eventService; - @Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 + @Scheduled(cron = "0 0 * * * *") // 매 시간마다 실행 public void updateEventStatuses() { eventService.updateEventStatus(); } From 4e60a4f4ca31474563abef500df33da316be66c6 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 12 Aug 2024 03:43:50 +0900 Subject: [PATCH 6/9] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0(?= =?UTF-8?q?=EC=BB=B4=ED=8C=8C=EC=9D=BC=20=EC=97=90=EB=9F=AC=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6983469..0d09a05 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' - implementation 'com.fasterxml.jackson.core:jackson-core:2.16.1' implementation 'org.springframework.boot:spring-boot-starter-mail' //JWT dependencies @@ -64,3 +63,8 @@ tasks.named('test') { jar { enabled = false } + +tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" +} + From 08a02f61b7cbee51f70489f08fa0cd4be8950b1a Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 12 Aug 2024 03:45:00 +0900 Subject: [PATCH 7/9] =?UTF-8?q?Feat:=20=EA=B0=81=20API=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=ED=94=8C=EB=9E=98=EA=B7=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../suppin/event/crawl/controller/CommentApi.java | 12 ++++++------ .../suppin/event/crawl/controller/CrawlApi.java | 10 +++++----- .../suppin/event/events/controller/EventApi.java | 2 +- .../suppin/event/survey/controller/SurveyApi.java | 14 +++++++------- .../cmc/suppin/member/controller/MemberApi.java | 6 +++--- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/cmc/suppin/event/crawl/controller/CommentApi.java b/src/main/java/com/cmc/suppin/event/crawl/controller/CommentApi.java index 881fda9..49a88c5 100644 --- a/src/main/java/com/cmc/suppin/event/crawl/controller/CommentApi.java +++ b/src/main/java/com/cmc/suppin/event/crawl/controller/CommentApi.java @@ -32,12 +32,12 @@ public class CommentApi { @Operation(summary = "크롤링된 전체 댓글 조회 API", description = "주어진 이벤트 ID와 URL의 댓글을 페이지네이션하여 이벤트의 endDate 전에 작성된 댓글들만 조회합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> getComments( - @RequestParam Long eventId, - @RequestParam String url, + @RequestParam("eventId") Long eventId, + @RequestParam("url") String url, @Parameter(description = "조회할 페이지 번호 (1부터 시작)") - @RequestParam int page, + @RequestParam("page") int page, @Parameter(description = "한 페이지당 댓글 수") - @RequestParam int size, + @RequestParam("size") int size, @CurrentAccount Account account) { CommentResponseDTO.CrawledCommentListDTO comments = commentService.getComments(eventId, url, page, size, account.userId()); return ResponseEntity.ok(ApiResponse.of(comments)); @@ -55,8 +55,8 @@ public ResponseEntity> drawWin @GetMapping("/winners/keywordFiltering") @Operation(summary = "키워드별 당첨자 조회 API", description = "주어진 키워드에 따라 1차 랜덤 추첨된 당첨자 중에서 키워드가 포함된 당첨자들을 조회합니다.") public ResponseEntity>> getWinnersByKeyword( - @RequestParam Long eventId, - @RequestParam String keyword, + @RequestParam("eventId") Long eventId, + @RequestParam("keyword") String keyword, @CurrentAccount Account account) { List filteredWinners = commentService.getCommentsByKeyword(eventId, keyword, account.userId()); return ResponseEntity.ok(ApiResponse.of(filteredWinners)); diff --git a/src/main/java/com/cmc/suppin/event/crawl/controller/CrawlApi.java b/src/main/java/com/cmc/suppin/event/crawl/controller/CrawlApi.java index babf7d2..d2948e7 100644 --- a/src/main/java/com/cmc/suppin/event/crawl/controller/CrawlApi.java +++ b/src/main/java/com/cmc/suppin/event/crawl/controller/CrawlApi.java @@ -28,12 +28,11 @@ public class CrawlApi { @Operation(summary = "크롤링 중복 검증 API", description = "주어진 URL과 eventId로 중복된 댓글 수집 이력이 있는지 확인합니다.

" + "Request
" + - "- url: 중복 검증할 URL
" + - "- eventId: 댓글 이벤트 생성 후 입력 받은 eventId

" + + "- url: 중복 검증할 URL

" + "Response
" + "- 요청된 URL과 중복된 댓글 수집 이력이 있을 경우 '검증 및 확인되었습니다.' 출력
" + "- 요청된 URL과 중복된 댓글 수집 이력이 없을 경우 '수집 이력이 없습니다.' 출력") - public ResponseEntity> checkExistingComments(@RequestParam String url, @CurrentAccount Account account) { + public ResponseEntity> checkExistingComments(@RequestParam("url") String url, @CurrentAccount Account account) { String message = crawlService.checkExistingComments(url, account.userId()); if (message != null) { return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS, message)); @@ -47,11 +46,12 @@ public ResponseEntity> checkExistingComments(@RequestParam S description = "주어진 URL의 유튜브 댓글을 크롤링하여 해당 댓글 데이터를 DB에 저장합니다.

" + "Request: url: 크롤링할 URL, eventId: 댓글을 수집할 eventId, forceUpdate: 댓글을 강제로 업데이트할지 여부(Boolean), Authorization: JWT 토큰을 포함한 인증 헤더

" + "
" + - "- 동일한 URL에 대한 댓글 크롤링 요청이지만, 강제로 업데이트하겠다는 의미이기 때문에, 기존 댓글 데이터를 삭제하고 새로 등록합니다.

" + + "- 동일한 URL에 대한 댓글 크롤링 요청이지만 강제로 업데이트하겠다는 의미이기 때문에, 기존 댓글 데이터를 삭제하고 새로 등록합니다.

" + "
" + + "크롤링하려는 URL이 중복되지 않았을 때의 요청이기 때문에, 새로운 댓글을 크롤링합니다.
" + "- DB에 기존 댓글이 존재하는 경우: 크롤링을 중지하고 예외를 던집니다.
" + "- DB에 기존 댓글이 존재하지 않는 경우: 새로운 댓글을 크롤링하고 이를 DB에 저장합니다.") - public ResponseEntity> crawlYoutubeComments(@RequestParam String url, @RequestParam Long eventId, @RequestParam boolean forceUpdate, @CurrentAccount Account account) { + public ResponseEntity> crawlYoutubeComments(@RequestParam("url") String url, @RequestParam("eventId") Long eventId, @RequestParam("forceUpdate") boolean forceUpdate, @CurrentAccount Account account) { crawlService.crawlYoutubeComments(url, eventId, account.userId(), forceUpdate); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS, "댓글 수집이 완료되었습니다.")); } diff --git a/src/main/java/com/cmc/suppin/event/events/controller/EventApi.java b/src/main/java/com/cmc/suppin/event/events/controller/EventApi.java index a02b7a8..ad5b034 100644 --- a/src/main/java/com/cmc/suppin/event/events/controller/EventApi.java +++ b/src/main/java/com/cmc/suppin/event/events/controller/EventApi.java @@ -62,7 +62,7 @@ public ResponseEntity> updateEvent(@PathVariable Long eventId, @DeleteMapping("/{eventId}") @Operation(summary = "이벤트 삭제 API", description = "PathVariable: eventId, JWT 토큰만 주시면 됩니다.") - public ResponseEntity> deleteEvent(@PathVariable Long eventId, @CurrentAccount Account account) { + public ResponseEntity> deleteEvent(@PathVariable("eventId") Long eventId, @CurrentAccount Account account) { eventService.deleteEvent(eventId, account.userId()); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } diff --git a/src/main/java/com/cmc/suppin/event/survey/controller/SurveyApi.java b/src/main/java/com/cmc/suppin/event/survey/controller/SurveyApi.java index d8187f4..abba461 100644 --- a/src/main/java/com/cmc/suppin/event/survey/controller/SurveyApi.java +++ b/src/main/java/com/cmc/suppin/event/survey/controller/SurveyApi.java @@ -36,7 +36,7 @@ public ResponseEntity> creat @GetMapping("/{surveyId}") @Operation(summary = "설문지 조회 API", description = "생성된 설문지 전체 정보를 조회합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") - public ResponseEntity> getSurvey(@PathVariable Long surveyId) { + public ResponseEntity> getSurvey(@PathVariable("surveyId") Long surveyId) { SurveyResponseDTO.SurveyResultDTO response = surveyService.getSurvey(surveyId); return ResponseEntity.ok(ApiResponse.of(response)); } @@ -51,12 +51,12 @@ public ResponseEntity> saveSurveyAnswers(@RequestBody @Valid S @GetMapping("/{surveyId}/answers/{questionId}") @Operation(summary = "질문별 설문 응답 결과 조회 API", description = "특정 질문에 따라 해당 질문에 대한 설문 결과를 응답합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> getSurveyAnswers( - @PathVariable Long surveyId, - @PathVariable Long questionId, + @PathVariable("surveyId") Long surveyId, + @PathVariable("questionId") Long questionId, @Parameter(description = "페이지 번호(1부터 시작)", example = "1") - @RequestParam int page, + @RequestParam("page") int page, @Parameter(description = "페이지 크기", example = "10") - @RequestParam int size, + @RequestParam("size") int size, @CurrentAccount Account account) { SurveyResponseDTO.SurveyAnswerResultDTO response = surveyService.getSurveyAnswers(surveyId, questionId, page, size, account.userId()); return ResponseEntity.ok(ApiResponse.of(response)); @@ -75,14 +75,14 @@ public ResponseEntity> @GetMapping("/winners/{surveyId}/{participantId}") @Operation(summary = "당첨자 세부 정보 조회 API", description = "설문 이벤트의 당첨자(익명 참여자) 정보를 조회하며, 해당 참여자가 응답한 모든 설문 내용을 반환합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> getWinnerDetails( - @PathVariable Long surveyId, @PathVariable Long participantId) { + @PathVariable("surveyId") Long surveyId, @PathVariable("participantId") Long participantId) { SurveyResponseDTO.WinnerDetailDTO winnerDetails = surveyService.getWinnerDetails(surveyId, participantId); return ResponseEntity.ok(ApiResponse.of(winnerDetails)); } @DeleteMapping("/winners") @Operation(summary = "당첨자 리스트 삭제 API(당첨자 재추첨 시, 기존 당첨자 리스트를 삭제 후 진행 해야합니다.)", description = "해당 설문조사의 모든 당첨자들의 isWinner 값을 false로 변경합니다.") - public ResponseEntity> deleteWinners(@RequestParam Long surveyId) { + public ResponseEntity> deleteWinners(@RequestParam("surveyId") Long surveyId) { surveyService.deleteWinners(surveyId); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } diff --git a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java index 9872102..f8c7d3e 100644 --- a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java +++ b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java @@ -58,7 +58,7 @@ public ResponseEntity> verifyEmailCode(@RequestBody @Valid Mem @GetMapping("/checkUserId") @Operation(summary = "아이디 중복 체크 API", description = "입력한 아이디가 중복된 id인지 검증합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.") @io.swagger.v3.oas.annotations.responses.ApiResponse(description = "중복이면 false, 중복 아니면 true") - public ResponseEntity> checkUserId(@RequestParam String userId) { + public ResponseEntity> checkUserId(@RequestParam("userId") String userId) { boolean checkUserId = memberService.confirmUserId(userId); return ResponseEntity.ok(ApiResponse.confirm(MemberConverter.toIdConfirmResultDTO(checkUserId))); @@ -67,7 +67,7 @@ public ResponseEntity> checkUs // 이메일 중복 체크 @GetMapping("/checkEmail") @Operation(summary = "이메일 중복 체크 API", description = "입력한 이메일이 중복된 이메일인지 검증합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.") - public ResponseEntity> checkEmail(@RequestParam String email) { + public ResponseEntity> checkEmail(@RequestParam("email") String email) { boolean checkEmail = memberService.confirmEmail(email); return ResponseEntity.ok(ApiResponse.confirm(MemberConverter.toEmailConfirmResultDTO(checkEmail))); @@ -111,7 +111,7 @@ public ResponseEntity> updatePassword(@RequestBody @Valid Memb // 현재 비밀번호 확인 @GetMapping("/password/check") @Operation(summary = "현재 비밀번호 확인 API", description = "request : password") - public ResponseEntity> checkPassword(@RequestParam String password, @CurrentAccount Account account) { + public ResponseEntity> checkPassword(@RequestParam("password") String password, @CurrentAccount Account account) { memberService.checkPassword(password, account.id()); return ResponseEntity.ok(ApiResponse.confirm(ResponseCode.CONFIRM)); } From c12acb00bf0aeb1a48b2582e5824bac08c0b7602 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 12 Aug 2024 03:46:51 +0900 Subject: [PATCH 8/9] =?UTF-8?q?Feat:=20=EB=B0=B0=ED=8F=AC=EC=9A=A9=20chrom?= =?UTF-8?q?edriver,=20firebase=20secret=20json=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cmc/suppin/event/crawl/service/CrawlService.java | 11 ++++++++--- .../com/cmc/suppin/fcm/config/FirebaseConfig.java | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cmc/suppin/event/crawl/service/CrawlService.java b/src/main/java/com/cmc/suppin/event/crawl/service/CrawlService.java index 9802302..62c4f46 100644 --- a/src/main/java/com/cmc/suppin/event/crawl/service/CrawlService.java +++ b/src/main/java/com/cmc/suppin/event/crawl/service/CrawlService.java @@ -72,8 +72,13 @@ public void crawlYoutubeComments(String url, Long eventId, String userId, boolea } } - // 크롤링 코드 실행 (생략) - System.setProperty("webdriver.chrome.driver", "src/main/resources/drivers/chromedriver"); + // 크롤링 코드 실행 + String chromeDriverPath = System.getenv("CHROME_DRIVER_PATH"); + if (chromeDriverPath != null && !chromeDriverPath.isEmpty()) { + System.setProperty("webdriver.chrome.driver", chromeDriverPath); + } else { + throw new RuntimeException("CHROME_DRIVER_PATH 환경 변수가 설정되지 않았습니다."); + } ChromeOptions options = new ChromeOptions(); options.addArguments("--headless"); @@ -95,7 +100,7 @@ public void crawlYoutubeComments(String url, Long eventId, String userId, boolea try { Thread.sleep(5000); // 초기 로딩 대기 - long endTime = System.currentTimeMillis() + 240000; // 스크롤 시간 조정 (필요에 따라 조정) + long endTime = System.currentTimeMillis() + 300000; // 스크롤 시간 조정 (필요에 따라 조정) JavascriptExecutor jsExecutor = (JavascriptExecutor) driver; while (System.currentTimeMillis() < endTime) { diff --git a/src/main/java/com/cmc/suppin/fcm/config/FirebaseConfig.java b/src/main/java/com/cmc/suppin/fcm/config/FirebaseConfig.java index 3853038..b399522 100644 --- a/src/main/java/com/cmc/suppin/fcm/config/FirebaseConfig.java +++ b/src/main/java/com/cmc/suppin/fcm/config/FirebaseConfig.java @@ -3,18 +3,23 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; import javax.annotation.PostConstruct; +import java.io.FileInputStream; import java.io.InputStream; @Configuration public class FirebaseConfig { + + @Value("${FIREBASE_CONFIG_PATH}") + private String firebaseConfigPath; + @PostConstruct public void init() { try { - InputStream serviceAccount = new ClassPathResource("suppin-a5657-firebase-adminsdk.json").getInputStream(); + InputStream serviceAccount = new FileInputStream(firebaseConfigPath); FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .build(); From c0359b3f52b896cc901fdad2ee1d43f7966e7672 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 12 Aug 2024 03:47:18 +0900 Subject: [PATCH 9/9] =?UTF-8?q?Chore:=20FCM=20=EA=B4=80=EB=A0=A8=20Swagger?= =?UTF-8?q?=20Tag=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/cmc/suppin/fcm/controller/FcmController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/cmc/suppin/fcm/controller/FcmController.java b/src/main/java/com/cmc/suppin/fcm/controller/FcmController.java index 738d151..69c04fa 100644 --- a/src/main/java/com/cmc/suppin/fcm/controller/FcmController.java +++ b/src/main/java/com/cmc/suppin/fcm/controller/FcmController.java @@ -3,6 +3,7 @@ import com.cmc.suppin.fcm.controller.dto.FcmSendDTO; import com.cmc.suppin.fcm.service.FcmService; import com.cmc.suppin.global.response.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -15,6 +16,7 @@ @Slf4j @RestController +@Tag(name = "FCM", description = "FCM 푸시알림 관련 API") @RequestMapping("/api/v1/fcm") public class FcmController {