Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/9: Swagger API 명세 구체화 및 전반적인 API 수정 #31

Merged
merged 10 commits into from
Aug 9, 2024
28 changes: 11 additions & 17 deletions src/main/java/com/cmc/suppin/event/crawl/controller/CommentApi.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package com.cmc.suppin.event.crawl.controller;

import com.cmc.suppin.event.crawl.controller.dto.CommentRequestDTO;
import com.cmc.suppin.event.crawl.controller.dto.CommentResponseDTO;
import com.cmc.suppin.event.crawl.service.CommentService;
import com.cmc.suppin.global.response.ApiResponse;
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.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.util.List;

Expand All @@ -30,31 +30,25 @@ public class CommentApi {

@GetMapping("/list")
@Operation(summary = "크롤링된 전체 댓글 조회 API",
description = "주어진 이벤트 ID와 URL의 댓글을 페이지네이션하여 이벤트의 endDate 전에 작성된 댓글들만 조회합니다.<br><br>" +
"Request: eventId: 조회할 이벤트의 ID, url: 댓글을 조회할 유튜브 URL, page: 조회할 페이지 번호 (1부터 시작), " +
"size: 한 페이지당 댓글 수, Authorization: JWT 토큰을 포함한 인증 헤더<br>" +
"Response: totalCommentCount: 전체 댓글 수, participantCount: 현재 페이지에서 가져온 댓글 수, crawlTime: 댓글 조회(크롤링) 요청 시간, comments: 각 댓글의 상세 정보 배열" +
"author: 댓글 작성자, commentText: 댓글 내용, commentDate: 댓글 작성 시간")
description = "주어진 이벤트 ID와 URL의 댓글을 페이지네이션하여 이벤트의 endDate 전에 작성된 댓글들만 조회합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.")
public ResponseEntity<ApiResponse<CommentResponseDTO.CrawledCommentListDTO>> getComments(
@RequestParam Long eventId,
@RequestParam String url,
@Parameter(description = "조회할 페이지 번호 (1부터 시작)")
@RequestParam int page,
@Parameter(description = "한 페이지당 댓글 수")
@RequestParam int size,
@CurrentAccount Account account) {
CommentResponseDTO.CrawledCommentListDTO comments = commentService.getComments(eventId, url, page, size, account.userId());
return ResponseEntity.ok(ApiResponse.of(comments));
}

@GetMapping("/draft-winners")
@Operation(summary = "조건별 당첨자 추첨 API", description = "주어진 조건에 따라 이벤트의 당첨자를 추첨합니다.")
@PostMapping("/draft-winners")
@Operation(summary = "조건별 당첨자 추첨 API(댓글 이벤트)", description = "주어진 조건에 따라 이벤트의 당첨자를 추첨합니다.")
public ResponseEntity<ApiResponse<CommentResponseDTO.WinnerResponseDTO>> drawWinners(
@RequestParam Long eventId,
@RequestParam String startDate,
@RequestParam String endDate,
@RequestParam int winnerCount,
@RequestParam List<String> keywords,
@RequestBody @Valid CommentRequestDTO.WinnerRequestDTO request,
@CurrentAccount Account account) {
CommentResponseDTO.WinnerResponseDTO winners = commentService.drawWinners(eventId, startDate, endDate, winnerCount, keywords, account.userId());
CommentResponseDTO.WinnerResponseDTO winners = commentService.drawWinners(request, account.userId());
return ResponseEntity.ok(ApiResponse.of(winners));
}

Expand Down
14 changes: 10 additions & 4 deletions src/main/java/com/cmc/suppin/event/crawl/controller/CrawlApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ public class CrawlApi {
@GetMapping("/comments/checkUrl")
@Operation(summary = "크롤링 중복 검증 API",
description = "주어진 URL과 eventId로 중복된 댓글 수집 이력이 있는지 확인합니다.<br><br>" +
"Request: url: 중복 검증할 URL, eventId: 중복 검증할 이벤트 ID, Authorization: JWT 토큰을 포함한 인증 헤더<br>" +
"Response: 중복된 댓글 수집 이력이 있을 경우 message 출력, 없을 경우 null")
"Request<br>" +
"- url: 중복 검증할 URL<br>" +
"- eventId: 댓글 이벤트 생성 후 입력 받은 eventId<br><br>" +
"Response<br>" +
"- 요청된 URL과 중복된 댓글 수집 이력이 있을 경우 '검증 및 확인되었습니다.' 출력<br>" +
"- 요청된 URL과 중복된 댓글 수집 이력이 없을 경우 '수집 이력이 없습니다.' 출력")
public ResponseEntity<ApiResponse<String>> checkExistingComments(@RequestParam String url, @RequestParam Long eventId, @CurrentAccount Account account) {
String message = crawlService.checkExistingComments(url, eventId, account.userId());
if (message != null) {
Expand All @@ -40,9 +44,11 @@ public ResponseEntity<ApiResponse<String>> checkExistingComments(@RequestParam S
// 유튜브 댓글 크롤링(DB 저장)
@PostMapping("/crawling/comments")
@Operation(summary = "유튜브 댓글 크롤링 API",
description = "주어진 URL의 유튜브 댓글을 크롤링하여 DB에 저장합니다.<br><br>" +
description = "주어진 URL의 유튜브 댓글을 크롤링하여 해당 댓글 데이터를 DB에 저장합니다.<br><br>" +
"Request: url: 크롤링할 URL, eventId: 댓글을 수집할 eventId, forceUpdate: 댓글을 강제로 업데이트할지 여부(Boolean), Authorization: JWT 토큰을 포함한 인증 헤더 <br><br>" +
"forceUpdate 입력 값이 false일 때 설명 <br> " +
"<forceUpdate 입력 값이 true일 때> <br> " +
"- 동일한 URL에 대한 댓글 크롤링 요청이지만, 강제로 업데이트하겠다는 의미이기 때문에, 기존 댓글 데이터를 삭제하고 새로 등록합니다. <br><br>" +
"<forceUpdate 입력 값이 false일 때> <br> " +
"- DB에 기존 댓글이 존재하는 경우: 크롤링을 중지하고 예외를 던집니다. <br>" +
"- DB에 기존 댓글이 존재하지 않는 경우: 새로운 댓글을 크롤링하고 이를 DB에 저장합니다.")
public ResponseEntity<ApiResponse<String>> crawlYoutubeComments(@RequestParam String url, @RequestParam Long eventId, @RequestParam boolean forceUpdate, @CurrentAccount Account account) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cmc.suppin.event.crawl.controller.dto;

import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -26,9 +27,14 @@ public static class CommentListRequestDTO {
@Builder
public static class WinnerRequestDTO {
private Long eventId;
private int winnerCount;
private int minLength;

@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String startDate;
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String endDate;
private int winnerCount;

private List<String> keywords;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static class CommentDetailDTO {
@Builder
public static class WinnerResponseDTO {
private int winnerCount;
private int minLength;
private String startDate;
private String endDate;
private List<CommentDetailDTO> winners;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cmc.suppin.event.crawl.converter;

import com.cmc.suppin.event.crawl.controller.dto.CommentRequestDTO;
import com.cmc.suppin.event.crawl.controller.dto.CommentResponseDTO;
import com.cmc.suppin.event.crawl.domain.Comment;
import com.cmc.suppin.event.events.domain.Event;
Expand Down Expand Up @@ -42,15 +43,16 @@ public static CommentResponseDTO.CrawledCommentListDTO toCommentListDTO(List<Com
.build();
}

public static CommentResponseDTO.WinnerResponseDTO toWinnerResponseDTO(List<Comment> winners, int winnerCount, String startDate, String endDate) {
public static CommentResponseDTO.WinnerResponseDTO toWinnerResponseDTO(List<Comment> winners, CommentRequestDTO.WinnerRequestDTO request) {
List<CommentResponseDTO.CommentDetailDTO> winnerDetails = winners.stream()
.map(CommentConverter::toCommentDetailDTO)
.collect(Collectors.toList());

return CommentResponseDTO.WinnerResponseDTO.builder()
.winnerCount(winnerCount)
.startDate(startDate)
.endDate(endDate)
.winnerCount(request.getWinnerCount())
.minLength(request.getMinLength())
.startDate(request.getStartDate())
.endDate(request.getEndDate())
.winners(winnerDetails)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cmc.suppin.event.crawl.service;

import com.cmc.suppin.event.crawl.controller.dto.CommentRequestDTO;
import com.cmc.suppin.event.crawl.controller.dto.CommentResponseDTO;
import com.cmc.suppin.event.crawl.converter.CommentConverter;
import com.cmc.suppin.event.crawl.domain.Comment;
Expand All @@ -18,9 +19,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -49,41 +48,36 @@ public CommentResponseDTO.CrawledCommentListDTO getComments(Long eventId, String

int totalComments = commentRepository.countByEventIdAndUrl(eventId, url);

String crawlTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
String crawlTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy. MM. dd HH:mm"));

return CommentConverter.toCommentListDTO(comments.getContent(), crawlTime, totalComments);
}

// 당첨자 조건별 랜덤 추첨
public CommentResponseDTO.WinnerResponseDTO drawWinners(Long eventId, String startDate, String endDate, int winnerCount, List<String> keywords, String userId) {
// 당첨자 조건별 랜덤 추첨(댓글 이벤트)
public CommentResponseDTO.WinnerResponseDTO drawWinners(CommentRequestDTO.WinnerRequestDTO request, String userId) {
Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED)
.orElseThrow(() -> new IllegalArgumentException("Member not found"));

Event event = eventRepository.findByIdAndMemberId(eventId, member.getId())
Event event = eventRepository.findByIdAndMemberId(request.getEventId(), member.getId())
.orElseThrow(() -> new IllegalArgumentException("Event not found"));

// String을 LocalDate로 변환하고 LocalDateTime으로 변환
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate start = LocalDate.parse(startDate, dateFormatter);
LocalDate end = LocalDate.parse(endDate, dateFormatter);

// LocalDateTime으로 변환
LocalDateTime startDateTime = start.atStartOfDay();
LocalDateTime endDateTime = end.atTime(LocalTime.MAX);
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy. MM. dd HH:mm");
LocalDateTime startDateTime = LocalDateTime.parse(request.getStartDate(), dateTimeFormatter);
LocalDateTime endDateTime = LocalDateTime.parse(request.getEndDate(), dateTimeFormatter);

// 당첨자 추첨 로직
List<Comment> comments = commentRepository.findByEventIdAndCommentDateBetween(event.getId(), startDateTime, endDateTime);

// 키워드 필터링(OR 로직)
// 키워드 필터링(OR 로직) 및 minLength 필터링 추가
List<Comment> filteredComments = comments.stream()
.filter(comment -> keywords.stream().anyMatch(keyword -> comment.getCommentText().contains(keyword)))
.filter(comment -> request.getKeywords().stream().anyMatch(keyword -> comment.getCommentText().contains(keyword)))
.filter(comment -> comment.getCommentText().length() >= request.getMinLength())
.collect(Collectors.toList());

// 랜덤 추첨
Collections.shuffle(filteredComments);
List<Comment> winners = filteredComments.stream().limit(winnerCount).collect(Collectors.toList());
List<Comment> winners = filteredComments.stream().limit(request.getWinnerCount()).collect(Collectors.toList());

return CommentConverter.toWinnerResponseDTO(winners, winnerCount, startDate, endDate);
return CommentConverter.toWinnerResponseDTO(winners, request);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,22 @@ public class EventApi {
private final EventService eventService;

@GetMapping("/all")
@Operation(summary = "전체 이벤트 조회 API", description = "로그인한 사용자의 모든 이벤트와 설문 및 댓글 수를 조회합니다., JWT 토큰만 주시면 됩니다.")
@Operation(summary = "전체 이벤트 조회 API", description = "사용자의 모든 이벤트와 설문 및 댓글 수를 조회합니다.")
public ResponseEntity<ApiResponse<List<EventResponseDTO.EventInfoDTO>>> getAllEventsWithCounts(@CurrentAccount Account account) {
List<EventResponseDTO.EventInfoDTO> events = eventService.getAllEvents(account.userId());
return ResponseEntity.ok(ApiResponse.of(events));
}

@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)<br><br>" +
"Response로 제공되는 eventId를 이용하여 타 API들을 호출해주시면 됩니다.")
@Operation(summary = "댓글 이벤트 생성 API", description = "댓글 이벤트를 생성합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.")
public ResponseEntity<ApiResponse<EventResponseDTO.EventInfoDTO>> 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)<br><br>" +
"Response로 제공되는 eventId를 이용하여 타 API들을 호출해주시면 됩니다.")
@Operation(summary = "설문 이벤트 생성 API", description = "설문 이벤트를 생성합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.")
public ResponseEntity<ApiResponse<EventResponseDTO.EventInfoDTO>> createSurveyEvent(@RequestBody @Valid EventRequestDTO.SurveyEventCreateDTO request, @CurrentAccount Account account) {
Event event = eventService.createSurveyEvent(request, account.userId());
EventResponseDTO.EventInfoDTO response = EventConverter.toEventInfoDTO(event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.cmc.suppin.global.enums.EventType;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -17,17 +18,23 @@ public class EventRequestDTO {
public static class CommentEventCreateDTO {
@NotNull
private EventType type;

@NotEmpty
private String title;
@NotEmpty

private String description;

@NotEmpty
private String url;

@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String startDate;
@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String endDate;
@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String announcementDate;
}

Expand All @@ -38,15 +45,21 @@ public static class CommentEventCreateDTO {
public static class SurveyEventCreateDTO {
@NotNull
private EventType type;

@NotEmpty
private String title;

@NotEmpty
private String description;

@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String startDate;
@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String endDate;
@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String announcementDate;
}

Expand All @@ -60,8 +73,15 @@ public static class EventUpdateDTO {
private String title;
private String description;
private String url;

@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String startDate;
@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String endDate;
@NotEmpty
@Pattern(regexp = "\\d{4}\\. \\d{2}\\. \\d{2} \\d{2}:\\d{2}", message = "날짜 형식은 yyyy. MM. dd HH:mm 이어야 합니다.")
private String announcementDate;
}
}
Loading
Loading