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 eaf3042..d8f88fa 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 @@ -1,6 +1,7 @@ package com.cmc.suppin.event.events.controller; import com.cmc.suppin.event.events.controller.dto.EventRequestDTO; +import com.cmc.suppin.event.events.controller.dto.EventResponseDTO; import com.cmc.suppin.event.events.service.EventService; import com.cmc.suppin.global.response.ApiResponse; import com.cmc.suppin.global.response.ResponseCode; @@ -13,25 +14,56 @@ 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 org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @Slf4j @RequiredArgsConstructor @Validated @Tag(name = "Event", description = "Event 관련 API") -@RequestMapping("/api/v1/event") +@RequestMapping("/api/v1/events") public class EventApi { private final EventService eventService; - @PostMapping("/new") - @Operation(summary = "댓글 이벤트 생성 API", description = "request : eventType, title, url, startDate, endDate") - public ResponseEntity> createEvent(@RequestBody @Valid EventRequestDTO.CommentEventCreateDTO request, @CurrentAccount Account account) { - eventService.createEvent(request, account.userId()); + @GetMapping("/all") + @Operation(summary = "전체 이벤트 조회 API", description = "로그인한 사용자의 모든 이벤트와 설문 및 댓글 수를 조회합니다., JWT 토큰만 주시면 됩니다.") + 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)") + public ResponseEntity> createCommentEvent(@RequestBody @Valid EventRequestDTO.CommentEventCreateDTO request, @CurrentAccount Account account) { + eventService.createCommentEvent(request, account.userId()); + return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); + } + + @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)); + } + + @PutMapping("/{eventId}/update") + @Operation(summary = "이벤트 수정 API", description = "PathVariable: eventId, Request : title, description, url, startDate, endDate, announcementDate") + public ResponseEntity> updateEvent(@PathVariable Long eventId, @RequestBody @Valid EventRequestDTO.EventUpdateDTO request, @CurrentAccount Account account) { + eventService.updateEvent(eventId, request, account.userId()); + return ResponseEntity.ok(ApiResponse.of(ResponseCode.SUCCESS)); + } + + @DeleteMapping("/{eventId}") + @Operation(summary = "이벤트 삭제 API", description = "PathVariable: eventId, JWT 토큰만 주시면 됩니다.") + public ResponseEntity> deleteEvent(@PathVariable 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/events/controller/dto/EventRequestDTO.java b/src/main/java/com/cmc/suppin/event/events/controller/dto/EventRequestDTO.java index 31e2407..7a221b6 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 @@ -1,6 +1,8 @@ package com.cmc.suppin.event.events.controller.dto; import com.cmc.suppin.global.enums.EventType; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -13,8 +15,50 @@ public class EventRequestDTO { @AllArgsConstructor @Builder public static class CommentEventCreateDTO { + @NotNull private EventType type; + @NotEmpty private String title; + @NotEmpty + private String description; + @NotEmpty + private String url; + @NotEmpty + private String startDate; + @NotEmpty + private String endDate; + @NotEmpty + private String announcementDate; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class SurveyEventCreateDTO { + @NotNull + private EventType type; + @NotEmpty + private String title; + @NotEmpty + private String description; + @NotEmpty + private String startDate; + @NotEmpty + private String endDate; + @NotEmpty + private String announcementDate; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class EventUpdateDTO { + @NotNull + private EventType type; + private String title; + private String description; private String url; private String startDate; private String endDate; 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 089e6b8..3645962 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 @@ -1,5 +1,6 @@ package com.cmc.suppin.event.events.controller.dto; +import com.cmc.suppin.global.enums.EventStatus; import com.cmc.suppin.global.enums.EventType; import lombok.AllArgsConstructor; import lombok.Builder; @@ -12,8 +13,25 @@ public class EventResponseDTO { @Getter @NoArgsConstructor @AllArgsConstructor - public static class CommentEventDetailDTO { + public static class EventInfoDTO { + private Long eventId; + private EventType type; + private String title; + private String url; + private String startDate; + private String endDate; + private String announcementDate; + private Integer surveyCount; + private Integer commentCount; + private EventStatus status; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class CommentEventDetailDTO { private EventType type; private String title; private String url; 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 d21beab..9b828a1 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 @@ -3,32 +3,86 @@ import com.cmc.suppin.event.events.controller.dto.EventRequestDTO; import com.cmc.suppin.event.events.controller.dto.EventResponseDTO; import com.cmc.suppin.event.events.domain.Event; +import com.cmc.suppin.global.enums.EventType; import com.cmc.suppin.member.domain.Member; -import java.time.LocalDateTime; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; 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()) .title(request.getTitle()) + .description(request.getDescription()) .url(request.getUrl()) - .startDate(LocalDateTime.parse(request.getStartDate())) - .endDate(LocalDateTime.parse(request.getEndDate())) - .announcementDate(LocalDateTime.parse(request.getAnnouncementDate())) + .startDate(LocalDate.parse(request.getStartDate(), formatter).atStartOfDay()) + .endDate(LocalDate.parse(request.getEndDate(), formatter).atStartOfDay()) + .announcementDate(LocalDate.parse(request.getAnnouncementDate(), formatter).atStartOfDay()) + .member(member) + .build(); + } + + public static Event toSurveyEventEntity(EventRequestDTO.SurveyEventCreateDTO request, Member member) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return Event.builder() + .type(request.getType()) + .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()) .member(member) .build(); } public static EventResponseDTO.CommentEventDetailDTO toEventDetailDTO(Event event) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); return EventResponseDTO.CommentEventDetailDTO.builder() .type(event.getType()) .title(event.getTitle()) .url(event.getUrl()) - .startDate(event.getStartDate().toString()) - .endDate(event.getEndDate().toString()) - .announcementDate(event.getAnnouncementDate().toString()) + .startDate(event.getStartDate().format(formatter)) + .endDate(event.getEndDate().format(formatter)) + .announcementDate(event.getAnnouncementDate().format(formatter)) + .build(); + } + + public static EventResponseDTO.EventInfoDTO toEventInfoDTO(Event event) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return EventResponseDTO.EventInfoDTO.builder() + .eventId(event.getId()) + .type(event.getType()) + .title(event.getTitle()) + .url(event.getUrl()) + .startDate(event.getStartDate().format(formatter)) + .endDate(event.getEndDate().format(formatter)) + .announcementDate(event.getAnnouncementDate().format(formatter)) + .surveyCount(event.getSurveyList().size()) + .commentCount(event.getCommentList().size()) + .status(event.getStatus()) .build(); } + + public static Event toUpdatedEventEntity(EventRequestDTO.EventUpdateDTO request, Member member) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + 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()) + .member(member); + + // Only set URL if the event type is COMMENT + if (request.getType() == EventType.COMMENT) { + eventBuilder.url(request.getUrl()); + } + + return eventBuilder.build(); + } + + } 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 868400d..3a53fc9 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 @@ -39,7 +39,7 @@ public class Event extends BaseDateTimeEntity { @Column(columnDefinition = "VARCHAR(100)", nullable = false) private String title; - @Column(columnDefinition = "TEXT") + @Column(columnDefinition = "TEXT", nullable = false) private String description; @Enumerated(EnumType.STRING) @@ -66,6 +66,11 @@ public void setMember(Member member) { member.getEventList().add(this); } - // Getters and Setters + public void setStatus(EventStatus status) { + this.status = status; + } + public void setId(Long id) { + this.id = id; + } } diff --git a/src/main/java/com/cmc/suppin/event/events/domain/repository/EventRepository.java b/src/main/java/com/cmc/suppin/event/events/domain/repository/EventRepository.java index e431734..872950a 100644 --- a/src/main/java/com/cmc/suppin/event/events/domain/repository/EventRepository.java +++ b/src/main/java/com/cmc/suppin/event/events/domain/repository/EventRepository.java @@ -4,7 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; public interface EventRepository extends JpaRepository { List findByMemberId(Long memberId); + + Optional findByIdAndMemberId(Long id, Long memberId); + } 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 00869ac..d4ba61a 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 @@ -1,15 +1,22 @@ package com.cmc.suppin.event.events.service; 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.domain.repository.EventRepository; +import com.cmc.suppin.global.enums.EventStatus; import com.cmc.suppin.global.enums.UserStatus; import com.cmc.suppin.member.domain.Member; import com.cmc.suppin.member.domain.repository.MemberRepository; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor public class EventService { @@ -17,12 +24,70 @@ public class EventService { private final EventRepository eventRepository; private final MemberRepository memberRepository; - public void createEvent(EventRequestDTO.CommentEventCreateDTO request, String userId) { + // 이벤트 상태 업데이트 메서드 + @Transactional + public void updateEventStatus() { + LocalDateTime now = LocalDateTime.now(); + List events = eventRepository.findAll(); + + for (Event event : events) { + if (event.getEndDate().isBefore(now) && event.getStatus() != EventStatus.DONE) { + event.setStatus(EventStatus.DONE); + } else if (event.getEndDate().isAfter(now) && event.getStatus() != EventStatus.PROCESSING) { + event.setStatus(EventStatus.PROCESSING); + } + } + } + + public List getAllEvents(String userId) { + Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); + + List events = eventRepository.findByMemberId(member.getId()); + return events.stream() + .map(EventConverter::toEventInfoDTO) + .collect(Collectors.toList()); + } + + public void createCommentEvent(EventRequestDTO.CommentEventCreateDTO request, String userId) { Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED) .orElseThrow(() -> new IllegalArgumentException("Member not found")); Event event = EventConverter.toCommentEventEntity(request, member); event.setMember(member); + event.setStatus(EventStatus.PROCESSING); + eventRepository.save(event); + } + + public void createSurveyEvent(EventRequestDTO.SurveyEventCreateDTO request, String userId) { + Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); + + Event event = EventConverter.toSurveyEventEntity(request, member); + event.setMember(member); + event.setStatus(EventStatus.PROCESSING); eventRepository.save(event); } + + public void updateEvent(Long eventId, EventRequestDTO.EventUpdateDTO request, String userId) { + Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); + + Event event = eventRepository.findByIdAndMemberId(eventId, member.getId()) + .orElseThrow(() -> new IllegalArgumentException("Event not found")); + + Event updatedEvent = EventConverter.toUpdatedEventEntity(request, member); + updatedEvent.setId(event.getId()); // 유지하려는 ID 설정 + eventRepository.save(updatedEvent); + } + + public void deleteEvent(Long eventId, String userId) { + Member member = memberRepository.findByUserIdAndStatusNot(userId, UserStatus.DELETED) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); + + Event event = eventRepository.findByIdAndMemberId(eventId, member.getId()) + .orElseThrow(() -> new IllegalArgumentException("Event not found")); + + eventRepository.delete(event); + } } 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 new file mode 100644 index 0000000..f7f5ca2 --- /dev/null +++ b/src/main/java/com/cmc/suppin/event/events/service/ScheduledTasks.java @@ -0,0 +1,17 @@ +package com.cmc.suppin.event.events.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ScheduledTasks { + + private final EventService eventService; + + @Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 + public void updateEventStatuses() { + eventService.updateEventStatus(); + } +} diff --git a/src/main/java/com/cmc/suppin/global/config/SchedulingConfig.java b/src/main/java/com/cmc/suppin/global/config/SchedulingConfig.java new file mode 100644 index 0000000..9343206 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/config/SchedulingConfig.java @@ -0,0 +1,9 @@ +package com.cmc.suppin.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SchedulingConfig { +} diff --git a/src/main/java/com/cmc/suppin/member/service/MemberService.java b/src/main/java/com/cmc/suppin/member/service/MemberService.java index d886758..6940584 100644 --- a/src/main/java/com/cmc/suppin/member/service/MemberService.java +++ b/src/main/java/com/cmc/suppin/member/service/MemberService.java @@ -41,16 +41,6 @@ public class MemberService { private final EmailVerificationTokenRepository emailVerificationTokenRepository; private final MailConfig mailConfig; - public boolean requestEmailVerification(String email) { - String code = generateVerificationCode(); - saveVerificationToken(email, code); - return mailConfig.sendMail(email, code); - } - - public boolean verifyEmailCode(String email, String code) { - return verifyToken(email, code); - } - /** * 회원가입 */ @@ -140,6 +130,45 @@ public void logout(Long accountId) { } } + /** + * 이메일 인증번호 요청 + */ + public boolean requestEmailVerification(String email) { + String code = generateVerificationCode(); + saveVerificationToken(email, code); + return mailConfig.sendMail(email, code); + } + + public boolean verifyEmailCode(String email, String code) { + boolean isValid = verifyToken(email, code); + if (!isValid) { + throw new IllegalArgumentException("이메일 인증에 실패하였습니다."); + } + return isValid; + } + + private boolean verifyToken(String email, String token) { + Optional verificationTokenOpt = emailVerificationTokenRepository.findByEmailAndToken(email, token); + if (verificationTokenOpt.isPresent()) { + EmailVerificationToken verificationToken = verificationTokenOpt.get(); + boolean isNotExpired = !verificationToken.isExpired(); + return isNotExpired; + } + return false; + } + + private void saveVerificationToken(String email, String code) { + EmailVerificationToken verificationToken = EmailVerificationToken.builder() + .email(email) + .token(code) + .expiryDate(LocalDateTime.now().plusMinutes(5)) + .build(); + + emailVerificationTokenRepository.deleteByEmail(email); + emailVerificationTokenRepository.save(verificationToken); + } + + private Member getMember(Long memberId) { return memberRepository.findByIdAndStatusNot(memberId, UserStatus.DELETED) .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); @@ -168,26 +197,6 @@ private String generateVerificationCode() { return String.valueOf(code); } - private void saveVerificationToken(String email, String code) { - EmailVerificationToken verificationToken = EmailVerificationToken.builder() - .email(email) - .token(code) - .expiryDate(LocalDateTime.now().plusMinutes(5)) - .build(); - - emailVerificationTokenRepository.deleteByEmail(email); - emailVerificationTokenRepository.save(verificationToken); - } - - private boolean verifyToken(String email, String token) { - Optional verificationTokenOpt = emailVerificationTokenRepository.findByEmailAndToken(email, token); - if (verificationTokenOpt.isPresent()) { - EmailVerificationToken verificationToken = verificationTokenOpt.get(); - return !verificationToken.isExpired(); - } - return false; - } - /** * 검증 메서드 */ diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b593742..86f9082 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -16,6 +16,10 @@ spring: dialect: org.hibernate.dialect.MySQL8Dialect show_sql: true format_sql: true + task: + scheduling: + pool: + size: 10 mail: host: smtp.gmail.com diff --git a/src/test/java/com/cmc/suppin/SuppinApplicationTests.java b/src/test/java/com/cmc/suppin/SuppinApplicationTests.java deleted file mode 100644 index add6347..0000000 --- a/src/test/java/com/cmc/suppin/SuppinApplicationTests.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.cmc.suppin; - -import com.cmc.suppin.global.enums.UserStatus; -import com.cmc.suppin.member.domain.repository.MemberRepository; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -@SpringBootTest -class SuppinApplicationTests { - - @Autowired - private MemberRepository memberRepository; - - @Test - public void testExistsByUserIdAndStatusNot() { - // Given - String userId = "testUser"; - UserStatus status = UserStatus.DELETED; - - // When - boolean exists = memberRepository.existsByUserIdAndStatusNot(userId, status); - - // Then - assertThat(exists).isFalse(); - } -}