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 e520cb1..3ff2d6d 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 @@ -1,11 +1,11 @@ package com.cmc.suppin.event.crawl.controller; -import com.cmc.suppin.event.crawl.controller.dto.CrawlResponseDTO; import com.cmc.suppin.event.crawl.service.CrawlService; import com.cmc.suppin.global.response.ApiResponse; import com.cmc.suppin.global.response.ResponseCode; import com.cmc.suppin.global.security.reslover.Account; import com.cmc.suppin.global.security.reslover.CurrentAccount; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,9 +26,13 @@ public class CrawlApi { private final CrawlService crawlService; - @GetMapping("/crawling/youtube/comments") - public ResponseEntity> crawlYoutubeComments(@RequestParam String url, @CurrentAccount Account account) { - crawlService.crawlYoutubeComments(url, account.userId()); + // 유튜브 크롤링 + @GetMapping("/crawling/comments") + @Operation(summary = "유튜브 댓글 크롤링 API", description = "주어진 URL의 유튜브 댓글을 크롤링하고 DB에 저장합니다.") + public ResponseEntity> crawlYoutubeComments(@RequestParam String url, @RequestParam Long eventId, @CurrentAccount Account account) { + crawlService.crawlYoutubeComments(url, eventId, account.userId()); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } + + // TODO: 인스타그램 게시글 크롤링 } diff --git a/src/main/java/com/cmc/suppin/event/crawl/converter/CommentConverter.java b/src/main/java/com/cmc/suppin/event/crawl/converter/CommentConverter.java index e66c6c0..2c50b9d 100644 --- a/src/main/java/com/cmc/suppin/event/crawl/converter/CommentConverter.java +++ b/src/main/java/com/cmc/suppin/event/crawl/converter/CommentConverter.java @@ -1,4 +1,17 @@ package com.cmc.suppin.event.crawl.converter; +import com.cmc.suppin.event.crawl.domain.Comment; +import com.cmc.suppin.event.events.domain.Event; + public class CommentConverter { + + public static Comment toCommentEntity(String author, String text, String time, String url, Event event) { + return Comment.builder() + .author(author) + .commentText(text) + .commentDate(time) + .url(url) + .event(event) + .build(); + } } 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 164f089..07073c2 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 @@ -1,8 +1,13 @@ package com.cmc.suppin.event.crawl.service; -import com.cmc.suppin.event.crawl.controller.dto.CrawlResponseDTO; +import com.cmc.suppin.event.crawl.converter.CommentConverter; import com.cmc.suppin.event.crawl.domain.Comment; import com.cmc.suppin.event.crawl.domain.repository.CommentRepository; +import com.cmc.suppin.event.events.domain.Event; +import com.cmc.suppin.event.events.domain.repository.EventRepository; +import com.cmc.suppin.global.enums.UserStatus; +import com.cmc.suppin.member.domain.Member; +import com.cmc.suppin.member.domain.repository.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jsoup.Jsoup; @@ -16,9 +21,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Set; @Service @@ -28,11 +31,26 @@ public class CrawlService { private final CommentRepository commentRepository; + private final EventRepository eventRepository; + private final MemberRepository memberRepository; + + public void crawlYoutubeComments(String url, Long eventId, String userId) { + log.info("Start crawling comments for URL: {} by user: {}", url, userId); + + Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); + + log.info("Member found: {}", member.getId()); + + Event event = eventRepository.findByIdAndMemberId(eventId, member.getId()) + .orElseThrow(() -> new IllegalArgumentException("Event not found")); + + log.info("Event found: {}", event.getId()); - public List crawlYoutubeComments(String url, String userId) { System.setProperty("webdriver.chrome.driver", "src/main/resources/drivers/chromedriver"); ChromeOptions options = new ChromeOptions(); + options.addArguments("--headless"); options.addArguments("--disable-gpu"); options.addArguments("--no-sandbox"); options.addArguments("--disable-dev-shm-usage"); @@ -41,7 +59,6 @@ public List crawlYoutubeComments(String url, St WebDriver driver = new ChromeDriver(options); driver.get(url); - List commentList = new ArrayList<>(); Set uniqueComments = new HashSet<>(); try { @@ -65,25 +82,16 @@ public List crawlYoutubeComments(String url, St if (!uniqueComments.contains(text)) { uniqueComments.add(text); - commentList.add(new CrawlResponseDTO.CrawlResultDTO(author, text, time)); // 엔티티 저장 - Comment comment = Comment.builder() - .author(author) - .commentText(text) - .commentDate(time) - .url(url) - .build(); + Comment comment = CommentConverter.toCommentEntity(author, text, time, url, event); commentRepository.save(comment); + log.info("Comment saved: {}", comment.getId()); } } } - - return commentList; - } catch (InterruptedException e) { e.printStackTrace(); - return new ArrayList<>(); } finally { driver.quit(); } 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 d8f88fa..962b9e8 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 @@ -2,6 +2,8 @@ import com.cmc.suppin.event.events.controller.dto.EventRequestDTO; import com.cmc.suppin.event.events.controller.dto.EventResponseDTO; +import com.cmc.suppin.event.events.converter.EventConverter; +import com.cmc.suppin.event.events.domain.Event; import com.cmc.suppin.event.events.service.EventService; import com.cmc.suppin.global.response.ApiResponse; import com.cmc.suppin.global.response.ResponseCode; @@ -38,19 +40,23 @@ public ResponseEntity>> getAllEv @PostMapping("/new/comment/crawling") @Operation(summary = "댓글 이벤트 생성 API", description = "Request : type(ENUM 타입으로, 'COMMENT와 SURVEY' 둘 중 하나를 입력해주시면 됩니다), " + - "title, description, url, startDate(yyyy-MM-dd), endDate(yyyy-MM-dd), announcementDate(yyyy-MM-dd)") - public ResponseEntity> createCommentEvent(@RequestBody @Valid EventRequestDTO.CommentEventCreateDTO request, @CurrentAccount Account account) { - eventService.createCommentEvent(request, account.userId()); - return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); + "title, description, url, startDate(yyyy-MM-dd), endDate(yyyy-MM-dd), announcementDate(yyyy-MM-dd)

" + + "Response로 제공되는 eventId를 이용하여 타 API들을 호출해주시면 됩니다.") + public ResponseEntity> createCommentEvent(@RequestBody @Valid EventRequestDTO.CommentEventCreateDTO request, @CurrentAccount Account account) { + Event event = eventService.createCommentEvent(request, account.userId()); + EventResponseDTO.EventInfoDTO response = EventConverter.toEventInfoDTO(event); + return ResponseEntity.ok(ApiResponse.of(response)); } @PostMapping("/new/survey") @Operation(summary = "설문조사 이벤트 생성 API", description = "Request : type(ENUM 타입으로, 'COMMENT와 SURVEY' 둘 중 하나를 입력해주시면 됩니다), " + - "title, description, startDate(yyyy-MM-dd), endDate(yyyy-MM-dd), announcementDate(yyyy-MM-dd)") - public ResponseEntity> createSurveyEvent(@RequestBody @Valid EventRequestDTO.SurveyEventCreateDTO request, @CurrentAccount Account account) { - eventService.createSurveyEvent(request, account.userId()); - return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); + "title, description, startDate(yyyy-MM-dd), endDate(yyyy-MM-dd), announcementDate(yyyy-MM-dd)

" + + "Response로 제공되는 eventId를 이용하여 타 API들을 호출해주시면 됩니다.") + public ResponseEntity> createSurveyEvent(@RequestBody @Valid EventRequestDTO.SurveyEventCreateDTO request, @CurrentAccount Account account) { + Event event = eventService.createSurveyEvent(request, account.userId()); + EventResponseDTO.EventInfoDTO response = EventConverter.toEventInfoDTO(event); + return ResponseEntity.ok(ApiResponse.of(response)); } @PutMapping("/{eventId}/update") diff --git a/src/main/java/com/cmc/suppin/event/events/controller/dto/EventResponseDTO.java b/src/main/java/com/cmc/suppin/event/events/controller/dto/EventResponseDTO.java index 3645962..2bb0ca8 100644 --- a/src/main/java/com/cmc/suppin/event/events/controller/dto/EventResponseDTO.java +++ b/src/main/java/com/cmc/suppin/event/events/controller/dto/EventResponseDTO.java @@ -7,6 +7,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.Optional; + public class EventResponseDTO { @Builder @@ -17,7 +19,7 @@ public static class EventInfoDTO { private Long eventId; private EventType type; private String title; - private String url; + private Optional url; private String startDate; private String endDate; private String announcementDate; diff --git a/src/main/java/com/cmc/suppin/event/events/converter/EventConverter.java b/src/main/java/com/cmc/suppin/event/events/converter/EventConverter.java index 9b828a1..ae76d93 100644 --- a/src/main/java/com/cmc/suppin/event/events/converter/EventConverter.java +++ b/src/main/java/com/cmc/suppin/event/events/converter/EventConverter.java @@ -8,13 +8,14 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.Optional; public class EventConverter { public static Event toCommentEventEntity(EventRequestDTO.CommentEventCreateDTO request, Member member) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); return Event.builder() - .type(request.getType()) + .type(EventType.COMMENT) .title(request.getTitle()) .description(request.getDescription()) .url(request.getUrl()) @@ -28,7 +29,7 @@ public static Event toCommentEventEntity(EventRequestDTO.CommentEventCreateDTO r public static Event toSurveyEventEntity(EventRequestDTO.SurveyEventCreateDTO request, Member member) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); return Event.builder() - .type(request.getType()) + .type(EventType.SURVEY) .title(request.getTitle()) .description(request.getDescription()) .startDate(LocalDate.parse(request.getStartDate(), formatter).atStartOfDay()) @@ -52,11 +53,17 @@ public static EventResponseDTO.CommentEventDetailDTO toEventDetailDTO(Event even public static EventResponseDTO.EventInfoDTO toEventInfoDTO(Event event) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + Optional url = Optional.empty(); + if (event.getType() == EventType.COMMENT) { + url = Optional.ofNullable(event.getUrl()); + } + return EventResponseDTO.EventInfoDTO.builder() .eventId(event.getId()) .type(event.getType()) .title(event.getTitle()) - .url(event.getUrl()) + .url(url) .startDate(event.getStartDate().format(formatter)) .endDate(event.getEndDate().format(formatter)) .announcementDate(event.getAnnouncementDate().format(formatter)) diff --git a/src/main/java/com/cmc/suppin/event/events/domain/Event.java b/src/main/java/com/cmc/suppin/event/events/domain/Event.java index 3a53fc9..cf9d9a6 100644 --- a/src/main/java/com/cmc/suppin/event/events/domain/Event.java +++ b/src/main/java/com/cmc/suppin/event/events/domain/Event.java @@ -31,9 +31,11 @@ public class Event extends BaseDateTimeEntity { private Member member; @OneToMany(mappedBy = "event") + @Builder.Default private List surveyList = new ArrayList<>(); @OneToMany(mappedBy = "event") + @Builder.Default private List commentList = new ArrayList<>(); @Column(columnDefinition = "VARCHAR(100)", nullable = false) diff --git a/src/main/java/com/cmc/suppin/event/events/service/EventService.java b/src/main/java/com/cmc/suppin/event/events/service/EventService.java index d4ba61a..cefe85e 100644 --- a/src/main/java/com/cmc/suppin/event/events/service/EventService.java +++ b/src/main/java/com/cmc/suppin/event/events/service/EventService.java @@ -6,6 +6,7 @@ import com.cmc.suppin.event.events.domain.Event; import com.cmc.suppin.event.events.domain.repository.EventRepository; import com.cmc.suppin.global.enums.EventStatus; +import com.cmc.suppin.global.enums.EventType; import com.cmc.suppin.global.enums.UserStatus; import com.cmc.suppin.member.domain.Member; import com.cmc.suppin.member.domain.repository.MemberRepository; @@ -49,24 +50,32 @@ public List getAllEvents(String userId) { .collect(Collectors.toList()); } - public void createCommentEvent(EventRequestDTO.CommentEventCreateDTO request, String userId) { + public Event createCommentEvent(EventRequestDTO.CommentEventCreateDTO request, String userId) { Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED) .orElseThrow(() -> new IllegalArgumentException("Member not found")); + if (request.getType() != EventType.COMMENT) { + throw new IllegalArgumentException("Event type must be COMMENT"); + } + Event event = EventConverter.toCommentEventEntity(request, member); event.setMember(member); event.setStatus(EventStatus.PROCESSING); - eventRepository.save(event); + return eventRepository.save(event); } - public void createSurveyEvent(EventRequestDTO.SurveyEventCreateDTO request, String userId) { + public Event createSurveyEvent(EventRequestDTO.SurveyEventCreateDTO request, String userId) { Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED) .orElseThrow(() -> new IllegalArgumentException("Member not found")); + if (request.getType() != EventType.SURVEY) { + throw new IllegalArgumentException("Event type must be SURVEY"); + } + Event event = EventConverter.toSurveyEventEntity(request, member); event.setMember(member); event.setStatus(EventStatus.PROCESSING); - eventRepository.save(event); + return eventRepository.save(event); } public void updateEvent(Long eventId, EventRequestDTO.EventUpdateDTO request, String userId) { diff --git a/src/main/java/com/cmc/suppin/global/security/config/WebMvcConfig.java b/src/main/java/com/cmc/suppin/global/security/config/WebMvcConfig.java index 0ee074e..213fc00 100644 --- a/src/main/java/com/cmc/suppin/global/security/config/WebMvcConfig.java +++ b/src/main/java/com/cmc/suppin/global/security/config/WebMvcConfig.java @@ -35,8 +35,10 @@ private String[] getAllowOrigins() { "http://localhost:3000", "https://localhost:3000", "https://dev.suppin.store", + "https://api.suppin.store", "https://suppin.store", - "http://192.168.0.100:3000" // 모바일 디바이스의 IP 주소 + "http://192.168.200.120:3000", // 테스트 디바이스 IP 허용 + "https://coherent-midge-probably.ngrok-free.app" ).toArray(String[]::new); } }