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 b786930..881fda9 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 @@ -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; @@ -30,31 +30,25 @@ public class CommentApi { @GetMapping("/list") @Operation(summary = "크롤링된 전체 댓글 조회 API", - description = "주어진 이벤트 ID와 URL의 댓글을 페이지네이션하여 이벤트의 endDate 전에 작성된 댓글들만 조회합니다.

" + - "Request: eventId: 조회할 이벤트의 ID, url: 댓글을 조회할 유튜브 URL, page: 조회할 페이지 번호 (1부터 시작), " + - "size: 한 페이지당 댓글 수, Authorization: JWT 토큰을 포함한 인증 헤더
" + - "Response: totalCommentCount: 전체 댓글 수, participantCount: 현재 페이지에서 가져온 댓글 수, crawlTime: 댓글 조회(크롤링) 요청 시간, comments: 각 댓글의 상세 정보 배열" + - "author: 댓글 작성자, commentText: 댓글 내용, commentDate: 댓글 작성 시간") + description = "주어진 이벤트 ID와 URL의 댓글을 페이지네이션하여 이벤트의 endDate 전에 작성된 댓글들만 조회합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> 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> drawWinners( - @RequestParam Long eventId, - @RequestParam String startDate, - @RequestParam String endDate, - @RequestParam int winnerCount, - @RequestParam List 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)); } 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 9acadaf..000a95e 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 @@ -27,8 +27,12 @@ public class CrawlApi { @GetMapping("/comments/checkUrl") @Operation(summary = "크롤링 중복 검증 API", description = "주어진 URL과 eventId로 중복된 댓글 수집 이력이 있는지 확인합니다.

" + - "Request: url: 중복 검증할 URL, eventId: 중복 검증할 이벤트 ID, Authorization: JWT 토큰을 포함한 인증 헤더
" + - "Response: 중복된 댓글 수집 이력이 있을 경우 message 출력, 없을 경우 null") + "Request
" + + "- url: 중복 검증할 URL
" + + "- eventId: 댓글 이벤트 생성 후 입력 받은 eventId

" + + "Response
" + + "- 요청된 URL과 중복된 댓글 수집 이력이 있을 경우 '검증 및 확인되었습니다.' 출력
" + + "- 요청된 URL과 중복된 댓글 수집 이력이 없을 경우 '수집 이력이 없습니다.' 출력") public ResponseEntity> checkExistingComments(@RequestParam String url, @RequestParam Long eventId, @CurrentAccount Account account) { String message = crawlService.checkExistingComments(url, eventId, account.userId()); if (message != null) { @@ -40,9 +44,11 @@ public ResponseEntity> checkExistingComments(@RequestParam S // 유튜브 댓글 크롤링(DB 저장) @PostMapping("/crawling/comments") @Operation(summary = "유튜브 댓글 크롤링 API", - description = "주어진 URL의 유튜브 댓글을 크롤링하여 DB에 저장합니다.

" + + description = "주어진 URL의 유튜브 댓글을 크롤링하여 해당 댓글 데이터를 DB에 저장합니다.

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

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

" + + "
" + "- DB에 기존 댓글이 존재하는 경우: 크롤링을 중지하고 예외를 던집니다.
" + "- DB에 기존 댓글이 존재하지 않는 경우: 새로운 댓글을 크롤링하고 이를 DB에 저장합니다.") public ResponseEntity> crawlYoutubeComments(@RequestParam String url, @RequestParam Long eventId, @RequestParam boolean forceUpdate, @CurrentAccount Account account) { diff --git a/src/main/java/com/cmc/suppin/event/crawl/controller/dto/CommentRequestDTO.java b/src/main/java/com/cmc/suppin/event/crawl/controller/dto/CommentRequestDTO.java index c3b1028..6dd74de 100644 --- a/src/main/java/com/cmc/suppin/event/crawl/controller/dto/CommentRequestDTO.java +++ b/src/main/java/com/cmc/suppin/event/crawl/controller/dto/CommentRequestDTO.java @@ -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; @@ -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 keywords; } } diff --git a/src/main/java/com/cmc/suppin/event/crawl/controller/dto/CommentResponseDTO.java b/src/main/java/com/cmc/suppin/event/crawl/controller/dto/CommentResponseDTO.java index 30862ca..a8f5fcf 100644 --- a/src/main/java/com/cmc/suppin/event/crawl/controller/dto/CommentResponseDTO.java +++ b/src/main/java/com/cmc/suppin/event/crawl/controller/dto/CommentResponseDTO.java @@ -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 winners; 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 fb09af0..89871b6 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,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; @@ -42,15 +43,16 @@ public static CommentResponseDTO.CrawledCommentListDTO toCommentListDTO(List winners, int winnerCount, String startDate, String endDate) { + public static CommentResponseDTO.WinnerResponseDTO toWinnerResponseDTO(List winners, CommentRequestDTO.WinnerRequestDTO request) { List 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(); } diff --git a/src/main/java/com/cmc/suppin/event/crawl/service/CommentService.java b/src/main/java/com/cmc/suppin/event/crawl/service/CommentService.java index d3cf147..8093176 100644 --- a/src/main/java/com/cmc/suppin/event/crawl/service/CommentService.java +++ b/src/main/java/com/cmc/suppin/event/crawl/service/CommentService.java @@ -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; @@ -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; @@ -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 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 comments = commentRepository.findByEventIdAndCommentDateBetween(event.getId(), startDateTime, endDateTime); - // 키워드 필터링(OR 로직) + // 키워드 필터링(OR 로직) 및 minLength 필터링 추가 List 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 winners = filteredComments.stream().limit(winnerCount).collect(Collectors.toList()); + List winners = filteredComments.stream().limit(request.getWinnerCount()).collect(Collectors.toList()); - return CommentConverter.toWinnerResponseDTO(winners, winnerCount, startDate, endDate); + return CommentConverter.toWinnerResponseDTO(winners, request); } 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 962b9e8..a02b7a8 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 @@ -31,17 +31,14 @@ public class EventApi { private final EventService eventService; @GetMapping("/all") - @Operation(summary = "전체 이벤트 조회 API", description = "로그인한 사용자의 모든 이벤트와 설문 및 댓글 수를 조회합니다., JWT 토큰만 주시면 됩니다.") + @Operation(summary = "전체 이벤트 조회 API", description = "사용자의 모든 이벤트와 설문 및 댓글 수를 조회합니다.") public ResponseEntity>> getAllEventsWithCounts(@CurrentAccount Account account) { List 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)

" + - "Response로 제공되는 eventId를 이용하여 타 API들을 호출해주시면 됩니다.") + @Operation(summary = "댓글 이벤트 생성 API", description = "댓글 이벤트를 생성합니다. 자세한 요청 및 응답 형식은 노션 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); @@ -49,10 +46,7 @@ public ResponseEntity> createCommentE } @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)

" + - "Response로 제공되는 eventId를 이용하여 타 API들을 호출해주시면 됩니다.") + @Operation(summary = "설문 이벤트 생성 API", description = "설문 이벤트를 생성합니다. 자세한 요청 및 응답 형식은 노션 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); diff --git a/src/main/java/com/cmc/suppin/event/events/controller/dto/EventRequestDTO.java b/src/main/java/com/cmc/suppin/event/events/controller/dto/EventRequestDTO.java index 7a221b6..bafffd1 100644 --- a/src/main/java/com/cmc/suppin/event/events/controller/dto/EventRequestDTO.java +++ b/src/main/java/com/cmc/suppin/event/events/controller/dto/EventRequestDTO.java @@ -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; @@ -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; } @@ -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; } @@ -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; } } 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 ae76d93..c0e1bfd 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 @@ -6,41 +6,41 @@ import com.cmc.suppin.global.enums.EventType; import com.cmc.suppin.member.domain.Member; -import java.time.LocalDate; +import java.time.LocalDateTime; 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"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy. MM. dd HH:mm"); return Event.builder() .type(EventType.COMMENT) .title(request.getTitle()) .description(request.getDescription()) .url(request.getUrl()) - .startDate(LocalDate.parse(request.getStartDate(), formatter).atStartOfDay()) - .endDate(LocalDate.parse(request.getEndDate(), formatter).atStartOfDay()) - .announcementDate(LocalDate.parse(request.getAnnouncementDate(), formatter).atStartOfDay()) + .startDate(LocalDateTime.parse(request.getStartDate(), formatter)) + .endDate(LocalDateTime.parse(request.getEndDate(), formatter)) + .announcementDate(LocalDateTime.parse(request.getAnnouncementDate(), formatter)) .member(member) .build(); } public static Event toSurveyEventEntity(EventRequestDTO.SurveyEventCreateDTO request, Member member) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy. MM. dd HH:mm"); return Event.builder() .type(EventType.SURVEY) .title(request.getTitle()) .description(request.getDescription()) - .startDate(LocalDate.parse(request.getStartDate(), formatter).atStartOfDay()) - .endDate(LocalDate.parse(request.getEndDate(), formatter).atStartOfDay()) - .announcementDate(LocalDate.parse(request.getAnnouncementDate(), formatter).atStartOfDay()) + .startDate(LocalDateTime.parse(request.getStartDate(), formatter)) + .endDate(LocalDateTime.parse(request.getEndDate(), formatter)) + .announcementDate(LocalDateTime.parse(request.getAnnouncementDate(), formatter)) .member(member) .build(); } public static EventResponseDTO.CommentEventDetailDTO toEventDetailDTO(Event event) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy. MM. dd HH:mm"); return EventResponseDTO.CommentEventDetailDTO.builder() .type(event.getType()) .title(event.getTitle()) @@ -52,13 +52,18 @@ public static EventResponseDTO.CommentEventDetailDTO toEventDetailDTO(Event even } public static EventResponseDTO.EventInfoDTO toEventInfoDTO(Event event) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy. MM. dd HH:mm"); Optional url = Optional.empty(); if (event.getType() == EventType.COMMENT) { url = Optional.ofNullable(event.getUrl()); } + // 설문 응답 수 합산 + int surveyAnswerCount = event.getSurveyList().stream() + .mapToInt(survey -> survey.getAnonymousParticipantList().size()) + .sum(); + return EventResponseDTO.EventInfoDTO.builder() .eventId(event.getId()) .type(event.getType()) @@ -67,20 +72,20 @@ public static EventResponseDTO.EventInfoDTO toEventInfoDTO(Event event) { .startDate(event.getStartDate().format(formatter)) .endDate(event.getEndDate().format(formatter)) .announcementDate(event.getAnnouncementDate().format(formatter)) - .surveyCount(event.getSurveyList().size()) + .surveyCount(surveyAnswerCount) .commentCount(event.getCommentList().size()) .status(event.getStatus()) .build(); } public static Event toUpdatedEventEntity(EventRequestDTO.EventUpdateDTO request, Member member) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy. MM. dd HH:mm"); Event.EventBuilder eventBuilder = Event.builder() .title(request.getTitle()) .description(request.getDescription()) - .startDate(LocalDate.parse(request.getStartDate(), formatter).atStartOfDay()) - .endDate(LocalDate.parse(request.getEndDate(), formatter).atStartOfDay()) - .announcementDate(LocalDate.parse(request.getAnnouncementDate(), formatter).atStartOfDay()) + .startDate(LocalDateTime.parse(request.getStartDate(), formatter)) + .endDate(LocalDateTime.parse(request.getEndDate(), formatter)) + .announcementDate(LocalDateTime.parse(request.getAnnouncementDate(), formatter)) .member(member); // Only set URL if the event type is COMMENT 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 cf9d9a6..bd3e0e0 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 @@ -41,7 +41,7 @@ public class Event extends BaseDateTimeEntity { @Column(columnDefinition = "VARCHAR(100)", nullable = false) private String title; - @Column(columnDefinition = "TEXT", nullable = false) + @Column(columnDefinition = "TEXT") private String description; @Enumerated(EnumType.STRING) 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 16062b4..d8187f4 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 @@ -8,6 +8,7 @@ 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; @@ -27,33 +28,34 @@ public class SurveyApi { private final SurveyService surveyService; @PostMapping("/create") - @Operation(summary = "설문지 생성 API", description = "QuestionType(Enum): SUBJECTIVE(주관식), SINGLE_CHOICE(객관식(단일 선택)), MULTIPLE_CHOICE(객관식(복수 선택))") + @Operation(summary = "설문지 생성 API", description = "설문지를 생성합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> createSurvey(@RequestBody @Valid SurveyRequestDTO.SurveyCreateDTO request, @CurrentAccount Account account) { SurveyResponseDTO.SurveyCreateResponse response = surveyService.createSurvey(request, account.userId()); return ResponseEntity.ok(ApiResponse.of(response)); } @GetMapping("/{surveyId}") - @Operation(summary = "설문지 조회 API", description = "Request: 설문지 ID, Response: 설문지 정보

" + - "SUBJEVTIVE: 주관식, SINGLE_CHOICE: 객관식(단일 선택), MULTIPLE_CHOICE: 객관식(복수 선택)") + @Operation(summary = "설문지 조회 API", description = "생성된 설문지 전체 정보를 조회합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> getSurvey(@PathVariable Long surveyId) { SurveyResponseDTO.SurveyResultDTO response = surveyService.getSurvey(surveyId); return ResponseEntity.ok(ApiResponse.of(response)); } @PostMapping("/reply") - @Operation(summary = "설문 답변 등록 API") + @Operation(summary = "설문 답변 등록 API", description = "익명 참가자들의 설문 응답을 등록합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> saveSurveyAnswers(@RequestBody @Valid SurveyRequestDTO.SurveyAnswerDTO request) { surveyService.saveSurveyAnswers(request); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); } @GetMapping("/{surveyId}/answers/{questionId}") - @Operation(summary = "질문별 설문 응답 결과 조회 API", description = "Request: 설문지 ID와 질문 ID, Response: 해당 질문에 대한 응답 리스트") + @Operation(summary = "질문별 설문 응답 결과 조회 API", description = "특정 질문에 따라 해당 질문에 대한 설문 결과를 응답합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> getSurveyAnswers( @PathVariable Long surveyId, @PathVariable Long questionId, + @Parameter(description = "페이지 번호(1부터 시작)", example = "1") @RequestParam int page, + @Parameter(description = "페이지 크기", example = "10") @RequestParam int size, @CurrentAccount Account account) { SurveyResponseDTO.SurveyAnswerResultDTO response = surveyService.getSurveyAnswers(surveyId, questionId, page, size, account.userId()); @@ -61,7 +63,8 @@ public ResponseEntity> getS } @PostMapping("/draft") - @Operation(summary = "당첨자 랜덤 추첨 결과 리스트 조회 API(설문 이벤트)", description = "주관식 답변 중 조건을 설정하여 랜덤으로 당첨자를 추첨합니다.") + @Operation(summary = "당첨자 랜덤 추첨 결과 리스트 조회 API(설문 이벤트)", + description = "주관식 답변 중 조건을 설정하여 랜덤으로 당첨자를 추첨합니다. 추첨된 당첨자의 isWinner값이 True로 설정됩니다. " + "자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> selectRandomWinners( @RequestBody @Valid SurveyRequestDTO.RandomSelectionRequestDTO request, @CurrentAccount Account account) { SurveyResponseDTO.RandomSelectionResponseDTO response = surveyService.selectRandomWinners(request, account.userId()); @@ -70,7 +73,7 @@ public ResponseEntity> // 당첨자 세부 정보 조회 API @GetMapping("/winners/{surveyId}/{participantId}") - @Operation(summary = "당첨자 세부 정보 조회 API", description = "설문 이벤트의 당첨자(익명 참여자) 정보를 조회하며, 해당 참여자가 응답한 모든 설문 내용을 반환합니다.") + @Operation(summary = "당첨자 세부 정보 조회 API", description = "설문 이벤트의 당첨자(익명 참여자) 정보를 조회하며, 해당 참여자가 응답한 모든 설문 내용을 반환합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고해주세요.") public ResponseEntity> getWinnerDetails( @PathVariable Long surveyId, @PathVariable Long participantId) { SurveyResponseDTO.WinnerDetailDTO winnerDetails = surveyService.getWinnerDetails(surveyId, participantId); @@ -78,7 +81,7 @@ public ResponseEntity> getWinnerD } @DeleteMapping("/winners") - @Operation(summary = "당첨자 리스트 삭제 API", description = "해당 설문조사의 모든 당첨자들의 isWinner 값을 false로 변경합니다.") + @Operation(summary = "당첨자 리스트 삭제 API(당첨자 재추첨 시, 기존 당첨자 리스트를 삭제 후 진행 해야합니다.)", description = "해당 설문조사의 모든 당첨자들의 isWinner 값을 false로 변경합니다.") public ResponseEntity> deleteWinners(@RequestParam Long surveyId) { surveyService.deleteWinners(surveyId); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); diff --git a/src/main/java/com/cmc/suppin/event/survey/controller/dto/SurveyResponseDTO.java b/src/main/java/com/cmc/suppin/event/survey/controller/dto/SurveyResponseDTO.java index d4c2d65..a70df15 100644 --- a/src/main/java/com/cmc/suppin/event/survey/controller/dto/SurveyResponseDTO.java +++ b/src/main/java/com/cmc/suppin/event/survey/controller/dto/SurveyResponseDTO.java @@ -60,9 +60,9 @@ public static class PersonalInfoOptionDTO { public static class SurveyAnswerResultDTO { private Long questionId; private String questionText; - private List answers; private int totalPages; private long totalElements; + private List answers; @Getter @NoArgsConstructor diff --git a/src/main/java/com/cmc/suppin/event/survey/service/SurveyService.java b/src/main/java/com/cmc/suppin/event/survey/service/SurveyService.java index 8125467..aff87de 100644 --- a/src/main/java/com/cmc/suppin/event/survey/service/SurveyService.java +++ b/src/main/java/com/cmc/suppin/event/survey/service/SurveyService.java @@ -143,6 +143,7 @@ public SurveyResponseDTO.SurveyAnswerResultDTO getSurveyAnswers(Long surveyId, L return SurveyConverter.toSurveyAnswerResultDTO(question, answersPage); } + // 랜덤 추첨 당첨자 선정 @Transactional public SurveyResponseDTO.RandomSelectionResponseDTO selectRandomWinners(SurveyRequestDTO.RandomSelectionRequestDTO request, String userId) { // 사용자 식별 @@ -172,7 +173,7 @@ public SurveyResponseDTO.RandomSelectionResponseDTO selectRandomWinners(SurveyRe .limit(request.getWinnerCount()) .collect(Collectors.toList()); - // 당첨자 업데이트 및 WinnerDTO 생성 + // 당첨자 업데이트(isWinner -> True 설정) 및 WinnerDTO 생성 List winners = selectedWinners.stream() .map(answer -> { AnonymousParticipant participant = answer.getAnonymousParticipant(); 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 e0170e9..9872102 100644 --- a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java +++ b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java @@ -30,7 +30,7 @@ public class MemberApi { // 회원가입 @PostMapping("/join") - @Operation(summary = "회원가입 API", description = "Request: termsAgree, userId, password, name, phone, email, userType, verificationCode") + @Operation(summary = "회원가입 API", description = "아이디 중복검사 및 이메일 인증 후, 회원으로 등록합니다. 자세한 요청 및 응답에 대한 설명은 노션 API 문서를 참고하시면 됩니다.") public ResponseEntity> join(@RequestBody @Valid MemberRequestDTO.JoinDTO request) { Member member = memberService.join(request); @@ -39,7 +39,7 @@ public ResponseEntity> join(@Reques // 이메일 인증번호 요청(회원가입 시) @PostMapping("/join/email/auth") - @Operation(summary = "이메일 인증번호 요청(회원가입 시) API", description = "request : email(이메일을 입력하면 해당 이메일로 인증번호 전송), response: 인증번호 전송 성공 시 true, 실패 시 false") + @Operation(summary = "이메일 인증번호 요청(회원가입 시) API", description = "이메일 인증번호 요청 API입니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.") public ResponseEntity> requestEmailAuth(@RequestBody @Valid MemberRequestDTO.EmailRequestDTO request) { memberService.requestEmailVerification(request.getEmail()); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); @@ -47,7 +47,8 @@ public ResponseEntity> requestEmailAuth(@RequestBody @Valid Me // 이메일 인증번호 확인(회원가입 시) @PostMapping("/join/email/verification") - @Operation(summary = "이메일 인증번호 확인 API", description = "request : email, verificationCode(인증번호 유효기간은 5분입니다.), response: 인증번호 일치 시 true, 불일치 시 false") + @Operation(summary = "이메일 인증번호 확인 API", description = "올바른 이메일 인증번호인지 검증합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.

" + + "request : email, verificationCode(인증번호 유효기간은 5분입니다), response: 인증번호 일치 시 true, 불일치 시 false") public ResponseEntity> verifyEmailCode(@RequestBody @Valid MemberRequestDTO.EmailVerificationDTO request) { memberService.verifyEmailCode(request.getEmail(), request.getVerificationCode()); return ResponseEntity.ok(ApiResponse.confirm(ResponseCode.CONFIRM)); @@ -55,7 +56,8 @@ public ResponseEntity> verifyEmailCode(@RequestBody @Valid Mem // 아이디 중복 체크 @GetMapping("/checkUserId") - @Operation(summary = "아이디 중복 체크 API", description = "request : userId, response: 중복이면 false, 중복 아니면 true") + @Operation(summary = "아이디 중복 체크 API", description = "입력한 아이디가 중복된 id인지 검증합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(description = "중복이면 false, 중복 아니면 true") public ResponseEntity> checkUserId(@RequestParam String userId) { boolean checkUserId = memberService.confirmUserId(userId); @@ -64,7 +66,7 @@ public ResponseEntity> checkUs // 이메일 중복 체크 @GetMapping("/checkEmail") - @Operation(summary = "이메일 중복 체크 API", description = "request : email, response: 중복이면 false, 중복 아니면 true") + @Operation(summary = "이메일 중복 체크 API", description = "입력한 이메일이 중복된 이메일인지 검증합니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.") public ResponseEntity> checkEmail(@RequestParam String email) { boolean checkEmail = memberService.confirmEmail(email); @@ -74,7 +76,8 @@ public ResponseEntity> chec // 회원탈퇴 @DeleteMapping("/delete") - @Operation(summary = "회원탈퇴 API", description = "로그인 시 발급받은 토큰으로 인가 필요, Authentication 헤더에 토큰을 넣어서 요청") + @Operation(summary = "회원탈퇴 API", description = "회원탈퇴 시 API입니다. MemberStatus가 \"DELETE\"로 변경됩니다. 자세한 요청 및 응답 형식은 노션 API 문서를 참고하시면 됩니다.

" + + "로그인 시 발급받은 토큰으로 인가 필요, Authentication 헤더에 토큰을 넣어서 요청") public ResponseEntity> deleteMember(@CurrentAccount Account account) { memberService.deleteMember(account.id()); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); @@ -82,7 +85,7 @@ public ResponseEntity> deleteMember(@CurrentAccount Account ac // 로그인 @PostMapping("/login") - @Operation(summary = "로그인 API", description = "request : userId, password") + @Operation(summary = "로그인 API", description = "로그인 성공 시 JWT 토큰(유효기간 7일)을 발급받습니다. request : userId, password") public ResponseEntity> login(@RequestBody @Valid MemberRequestDTO.LoginRequestDTO request) { MemberResponseDTO.LoginResponseDTO response = memberService.login(request); @@ -91,7 +94,7 @@ public ResponseEntity> login(@Re // 로그아웃 @PostMapping("/logout") - @Operation(summary = "로그아웃 API", description = "로그인 시 발급받은 토큰으로 인가 필요, Authentication 헤더에 토큰을 넣어서 요청") + @Operation(summary = "로그아웃 API", description = "사용중인 토큰을 블랙리스트에 등록합니다(토큰 무효화). 로그인 시 발급받은 JWT 토큰으로 인가 필요, Authentication 헤더에 토큰을 넣어서 요청") public ResponseEntity> logout(@CurrentAccount Account account) { memberService.logout(account.id()); return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); @@ -115,7 +118,7 @@ public ResponseEntity> checkPass // 회원정보 상세 조회 @GetMapping("/me") - @Operation(summary = "회원정보 상세 조회 API", description = "로그인 시 발급받은 토큰으로 인가 필요, Authentication 헤더에 토큰을 넣어서 요청") + @Operation(summary = "회원정보 상세 조회 API", description = "회원 상세 정보를 조회합니다. 로그인 시 발급받은 토큰으로 인가 필요, Authentication 헤더에 토큰을 넣어서 요청") public ResponseEntity> getUserDetail(@CurrentAccount Account account) { MemberResponseDTO.MemberDetailsDTO memberDetails = memberService.getMemberDetails(account.id()); return ResponseEntity.ok(ApiResponse.of(memberDetails)); diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java index ac427eb..cf491cd 100644 --- a/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java @@ -66,6 +66,7 @@ public static class MemberDetailsDTO { private String name; private String email; private String phoneNumber; + private String userType; private LocalDateTime createdAt; } } diff --git a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java index 15bd279..6ecbcad 100644 --- a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java +++ b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java @@ -69,6 +69,7 @@ public static MemberResponseDTO.MemberDetailsDTO toMemberDetailsDTO(Member membe .name(member.getName()) .email(member.getEmail()) .phoneNumber(member.getPhoneNumber()) + .userType(member.getUserType()) .createdAt(member.getCreatedAt()) .build(); }