From c38b3572184a65814e43d41573fc13515e734da7 Mon Sep 17 00:00:00 2001 From: sanghoon Date: Fri, 3 Oct 2025 12:08:51 +0900 Subject: [PATCH 1/2] feat(#284): study settings api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. study settings api(단건 조회, 업데이트) 2. consumer(스터디 생성 시, 스터디 설정 생성) --- .../application/StudySettingService.java | 60 +++++++++++++++++++ .../consumer/StudySettingConsumer.java | 22 +++++++ .../application/res/StudySettingRes.java | 23 +++++++ .../studysettings/entity/StudySettings.java | 48 +++++++++++++++ .../exception/StudySettingsErrorCode.java | 32 ++++++++++ .../StudySettingJpaRepository.java | 10 ++++ .../StudySettingRepository.java | 12 ++++ .../StudySettingRepositoryImpl.java | 23 +++++++ .../presentation/StudySettingController.java | 46 ++++++++++++++ .../UpdateStudySettingRequestDto.java | 19 ++++++ 10 files changed, 295 insertions(+) create mode 100644 src/main/java/com/mos/backend/studysettings/application/StudySettingService.java create mode 100644 src/main/java/com/mos/backend/studysettings/application/consumer/StudySettingConsumer.java create mode 100644 src/main/java/com/mos/backend/studysettings/application/res/StudySettingRes.java create mode 100644 src/main/java/com/mos/backend/studysettings/entity/StudySettings.java create mode 100644 src/main/java/com/mos/backend/studysettings/entity/exception/StudySettingsErrorCode.java create mode 100644 src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingJpaRepository.java create mode 100644 src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingRepository.java create mode 100644 src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingRepositoryImpl.java create mode 100644 src/main/java/com/mos/backend/studysettings/presentation/StudySettingController.java create mode 100644 src/main/java/com/mos/backend/studysettings/presentation/requestdto/UpdateStudySettingRequestDto.java diff --git a/src/main/java/com/mos/backend/studysettings/application/StudySettingService.java b/src/main/java/com/mos/backend/studysettings/application/StudySettingService.java new file mode 100644 index 0000000..0766eff --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/application/StudySettingService.java @@ -0,0 +1,60 @@ +package com.mos.backend.studysettings.application; + +import com.mos.backend.common.exception.MosException; +import com.mos.backend.common.infrastructure.EntityFacade; +import com.mos.backend.studies.entity.Study; +import com.mos.backend.studysettings.application.res.StudySettingRes; +import com.mos.backend.studysettings.entity.StudySettings; +import com.mos.backend.studysettings.entity.exception.StudySettingsErrorCode; +import com.mos.backend.studysettings.infrastructure.StudySettingRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class StudySettingService { + private final StudySettingRepository studySettingRepository; + private final EntityFacade entityFacade; + + /** + * 스터디 설정 생성 + * @param studyId 생성할 스터디 설정의 스터디 id + */ + @Transactional + public void createStudySettings(Long studyId) { + Study study = entityFacade.getStudy(studyId); + StudySettings studySettings = StudySettings.create(study); + studySettingRepository.save(studySettings); + } + + /** + * 스터디 아이디로 스터디 설정 조회해서 업데이트 + * @param studyId 조회할 스터디 아이디 + * @param lateThresholdMinutes 수정할 지간 기준 시간 + * @param absenceThresholdMinutes 수정할 결석 기준 시간 + * @return + */ + @Transactional + @PreAuthorize("@studySecurity.isLeaderOrAdmin(#studyId)") + public StudySettingRes update(Long studyId, Integer lateThresholdMinutes, Integer absenceThresholdMinutes) { + StudySettings studySettings = studySettingRepository.findByStudyId(studyId) + .orElseThrow(() -> new MosException(StudySettingsErrorCode.NOT_FOUND)); + studySettings.update(lateThresholdMinutes, absenceThresholdMinutes); + return StudySettingRes.of(studySettings, studyId); + } + + /** + * 스터디 아이디로 스터디 설정 단 건 조회 + * @param studyId 조회할 스터디 설정의 스터디 아이디 + * @return 스터디 설정의 공용 응답 dto + */ + @Transactional + @PreAuthorize("@studySecurity.isMemberOrAdmin(#studyId)") + public StudySettingRes read(Long studyId) { + StudySettings studySettings = studySettingRepository.findByStudyId(studyId) + .orElseThrow(() -> new MosException(StudySettingsErrorCode.NOT_FOUND)); + return StudySettingRes.of(studySettings, studyId); + } +} diff --git a/src/main/java/com/mos/backend/studysettings/application/consumer/StudySettingConsumer.java b/src/main/java/com/mos/backend/studysettings/application/consumer/StudySettingConsumer.java new file mode 100644 index 0000000..61c5580 --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/application/consumer/StudySettingConsumer.java @@ -0,0 +1,22 @@ +package com.mos.backend.studysettings.application.consumer; + +import com.mos.backend.common.event.Event; +import com.mos.backend.studies.application.event.StudyCreatedEventPayload; +import com.mos.backend.studysettings.application.StudySettingService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionalEventListener; + +import static org.springframework.transaction.event.TransactionPhase.BEFORE_COMMIT; + +@RequiredArgsConstructor +@Component +public class StudySettingConsumer { + private final StudySettingService studySettingService; + + @TransactionalEventListener(phase = BEFORE_COMMIT) + public void handleStudyCreatedEvent(Event event) { + StudyCreatedEventPayload payload = event.getPayload(); + studySettingService.createStudySettings(payload.getStudyId()); + } +} diff --git a/src/main/java/com/mos/backend/studysettings/application/res/StudySettingRes.java b/src/main/java/com/mos/backend/studysettings/application/res/StudySettingRes.java new file mode 100644 index 0000000..1d9c46f --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/application/res/StudySettingRes.java @@ -0,0 +1,23 @@ +package com.mos.backend.studysettings.application.res; + +import com.mos.backend.studysettings.entity.StudySettings; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class StudySettingRes { + private Long studySettingId; + private Long studyId; + private Integer lateThresholdMinutes; + private Integer absenceThresholdMinutes; + + public static StudySettingRes of(StudySettings studySettings, Long studyId) { + return new StudySettingRes( + studySettings.getId(), + studyId, + studySettings.getLateThresholdMinutes(), + studySettings.getAbsenceThresholdMinutes() + ); + } +} diff --git a/src/main/java/com/mos/backend/studysettings/entity/StudySettings.java b/src/main/java/com/mos/backend/studysettings/entity/StudySettings.java new file mode 100644 index 0000000..d07a9f9 --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/entity/StudySettings.java @@ -0,0 +1,48 @@ +package com.mos.backend.studysettings.entity; + +import com.mos.backend.common.entity.BaseAuditableEntity; +import com.mos.backend.studies.entity.Study; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "study_settings") +public class StudySettings extends BaseAuditableEntity { + + public static final int DEFAULT_LATE_THRESHOLD_MINUTES = 15; + public static final int DEFAULT_ABSENCE_THRESHOLD_MINUTES = 60; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = false, name = "study_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private Study study; + + @Column(nullable = false) + private Integer lateThresholdMinutes; + + @Column(nullable = false) + private Integer absenceThresholdMinutes; + + public static StudySettings create(Study study) { + StudySettings studySettings = new StudySettings(); + studySettings.study = study; + studySettings.lateThresholdMinutes = DEFAULT_LATE_THRESHOLD_MINUTES; + studySettings.absenceThresholdMinutes = DEFAULT_ABSENCE_THRESHOLD_MINUTES; + return studySettings; + } + + public void update(Integer lateThresholdMinutes, Integer absenceThresholdMinutes) { + this.lateThresholdMinutes = lateThresholdMinutes; + this.absenceThresholdMinutes = absenceThresholdMinutes; + } +} \ No newline at end of file diff --git a/src/main/java/com/mos/backend/studysettings/entity/exception/StudySettingsErrorCode.java b/src/main/java/com/mos/backend/studysettings/entity/exception/StudySettingsErrorCode.java new file mode 100644 index 0000000..8c0d8e8 --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/entity/exception/StudySettingsErrorCode.java @@ -0,0 +1,32 @@ +package com.mos.backend.studysettings.entity.exception; + +import com.mos.backend.common.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.context.MessageSource; +import org.springframework.http.HttpStatus; + +import java.util.Locale; + +@RequiredArgsConstructor +public enum StudySettingsErrorCode implements ErrorCode { + NOT_FOUND(HttpStatus.NOT_FOUND, "study-settings.not-found") + ; + + private final HttpStatus httpStatus; + private final String messageKey; + + @Override + public HttpStatus getStatus() { + return httpStatus; + } + + @Override + public String getErrorName() { + return this.name(); + } + + @Override + public String getMessage(MessageSource messageSource) { + return messageSource.getMessage(messageKey, null, Locale.getDefault()); + } +} \ No newline at end of file diff --git a/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingJpaRepository.java b/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingJpaRepository.java new file mode 100644 index 0000000..2289419 --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingJpaRepository.java @@ -0,0 +1,10 @@ +package com.mos.backend.studysettings.infrastructure; + +import com.mos.backend.studysettings.entity.StudySettings; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface StudySettingJpaRepository extends JpaRepository { + Optional findByStudyId(Long studyId); +} \ No newline at end of file diff --git a/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingRepository.java b/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingRepository.java new file mode 100644 index 0000000..324e246 --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingRepository.java @@ -0,0 +1,12 @@ +package com.mos.backend.studysettings.infrastructure; + +import com.mos.backend.studysettings.entity.StudySettings; + +import java.util.Optional; + +public interface StudySettingRepository { + + void save(StudySettings studySettings); + + Optional findByStudyId(Long studyId); +} \ No newline at end of file diff --git a/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingRepositoryImpl.java b/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingRepositoryImpl.java new file mode 100644 index 0000000..6bf83d4 --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/infrastructure/StudySettingRepositoryImpl.java @@ -0,0 +1,23 @@ +package com.mos.backend.studysettings.infrastructure; + +import com.mos.backend.studysettings.entity.StudySettings; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@RequiredArgsConstructor +@Repository +public class StudySettingRepositoryImpl implements StudySettingRepository{ + private final StudySettingJpaRepository studySettingJpaRepository; + + @Override + public void save(StudySettings studySettings) { + studySettingJpaRepository.save(studySettings); + } + + @Override + public Optional findByStudyId(Long studyId) { + return studySettingJpaRepository.findByStudyId(studyId); + } +} \ No newline at end of file diff --git a/src/main/java/com/mos/backend/studysettings/presentation/StudySettingController.java b/src/main/java/com/mos/backend/studysettings/presentation/StudySettingController.java new file mode 100644 index 0000000..68e906b --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/presentation/StudySettingController.java @@ -0,0 +1,46 @@ +package com.mos.backend.studysettings.presentation; + +import com.mos.backend.studysettings.application.StudySettingService; +import com.mos.backend.studysettings.application.res.StudySettingRes; +import com.mos.backend.studysettings.presentation.requestdto.UpdateStudySettingRequestDto; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping +public class StudySettingController { + + private final StudySettingService studySettingService; + + /** + * 스터디 아이디로 해당 스터디 설정 조회 + * @param studyId + * @return + */ + @GetMapping("/studies/{studyId}/study-settings") + @ResponseStatus(HttpStatus.OK) + public StudySettingRes read( + @PathVariable Long studyId + ) { + return studySettingService.read(studyId); + } + + /** + * 스터디 아이디로 조회해서 스터디 설정 수정 + * 지각 기준 시간, 결석 기준 시간 수정 + * @param studyId + * @param requestDto + * @return + */ + @PatchMapping("/studies/{studyId}/study-settings") + @ResponseStatus(HttpStatus.OK) + public StudySettingRes update( + @PathVariable Long studyId, + @Valid @RequestBody UpdateStudySettingRequestDto requestDto + ) { + return studySettingService.update(studyId, requestDto.getLateThresholdMinutes(), requestDto.getAbsenceThresholdMinutes()); + } +} diff --git a/src/main/java/com/mos/backend/studysettings/presentation/requestdto/UpdateStudySettingRequestDto.java b/src/main/java/com/mos/backend/studysettings/presentation/requestdto/UpdateStudySettingRequestDto.java new file mode 100644 index 0000000..377ae8b --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/presentation/requestdto/UpdateStudySettingRequestDto.java @@ -0,0 +1,19 @@ +package com.mos.backend.studysettings.presentation.requestdto; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class UpdateStudySettingRequestDto { + @NotNull(message = "lateThresholdMinutes는 필수입니다.") + @Positive(message = "lateThresholdMiniutes는 0보다 커야합니다.") + private Integer lateThresholdMinutes; + @NotNull(message = "absenceThresholdMinutes는 필수입니다.") + @Positive(message = "absenceThresholdMinutes는 0보다 커야합니다.") + private Integer absenceThresholdMinutes; +} From 750e97996c1a45023a782f1c905730bc2b66f0b6 Mon Sep 17 00:00:00 2001 From: sanghoon Date: Wed, 8 Oct 2025 20:49:15 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor(#284):=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../studysettings/application/StudySettingService.java | 2 +- .../application/consumer/StudySettingConsumer.java | 2 +- .../studysettings/presentation/StudySettingController.java | 4 ++-- ...tingRequestDto.java => StudySettingUpdateRequestDto.java} | 2 +- src/main/resources/messages/messages-error.properties | 5 ++++- 5 files changed, 9 insertions(+), 6 deletions(-) rename src/main/java/com/mos/backend/studysettings/presentation/requestdto/{UpdateStudySettingRequestDto.java => StudySettingUpdateRequestDto.java} (93%) diff --git a/src/main/java/com/mos/backend/studysettings/application/StudySettingService.java b/src/main/java/com/mos/backend/studysettings/application/StudySettingService.java index 0766eff..cdea9b8 100644 --- a/src/main/java/com/mos/backend/studysettings/application/StudySettingService.java +++ b/src/main/java/com/mos/backend/studysettings/application/StudySettingService.java @@ -23,7 +23,7 @@ public class StudySettingService { * @param studyId 생성할 스터디 설정의 스터디 id */ @Transactional - public void createStudySettings(Long studyId) { + public void createStudySetting(Long studyId) { Study study = entityFacade.getStudy(studyId); StudySettings studySettings = StudySettings.create(study); studySettingRepository.save(studySettings); diff --git a/src/main/java/com/mos/backend/studysettings/application/consumer/StudySettingConsumer.java b/src/main/java/com/mos/backend/studysettings/application/consumer/StudySettingConsumer.java index 61c5580..72db85a 100644 --- a/src/main/java/com/mos/backend/studysettings/application/consumer/StudySettingConsumer.java +++ b/src/main/java/com/mos/backend/studysettings/application/consumer/StudySettingConsumer.java @@ -17,6 +17,6 @@ public class StudySettingConsumer { @TransactionalEventListener(phase = BEFORE_COMMIT) public void handleStudyCreatedEvent(Event event) { StudyCreatedEventPayload payload = event.getPayload(); - studySettingService.createStudySettings(payload.getStudyId()); + studySettingService.createStudySetting(payload.getStudyId()); } } diff --git a/src/main/java/com/mos/backend/studysettings/presentation/StudySettingController.java b/src/main/java/com/mos/backend/studysettings/presentation/StudySettingController.java index 68e906b..6932688 100644 --- a/src/main/java/com/mos/backend/studysettings/presentation/StudySettingController.java +++ b/src/main/java/com/mos/backend/studysettings/presentation/StudySettingController.java @@ -2,7 +2,7 @@ import com.mos.backend.studysettings.application.StudySettingService; import com.mos.backend.studysettings.application.res.StudySettingRes; -import com.mos.backend.studysettings.presentation.requestdto.UpdateStudySettingRequestDto; +import com.mos.backend.studysettings.presentation.requestdto.StudySettingUpdateRequestDto; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -39,7 +39,7 @@ public StudySettingRes read( @ResponseStatus(HttpStatus.OK) public StudySettingRes update( @PathVariable Long studyId, - @Valid @RequestBody UpdateStudySettingRequestDto requestDto + @Valid @RequestBody StudySettingUpdateRequestDto requestDto ) { return studySettingService.update(studyId, requestDto.getLateThresholdMinutes(), requestDto.getAbsenceThresholdMinutes()); } diff --git a/src/main/java/com/mos/backend/studysettings/presentation/requestdto/UpdateStudySettingRequestDto.java b/src/main/java/com/mos/backend/studysettings/presentation/requestdto/StudySettingUpdateRequestDto.java similarity index 93% rename from src/main/java/com/mos/backend/studysettings/presentation/requestdto/UpdateStudySettingRequestDto.java rename to src/main/java/com/mos/backend/studysettings/presentation/requestdto/StudySettingUpdateRequestDto.java index 377ae8b..eae02ab 100644 --- a/src/main/java/com/mos/backend/studysettings/presentation/requestdto/UpdateStudySettingRequestDto.java +++ b/src/main/java/com/mos/backend/studysettings/presentation/requestdto/StudySettingUpdateRequestDto.java @@ -9,7 +9,7 @@ @Getter @AllArgsConstructor @NoArgsConstructor -public class UpdateStudySettingRequestDto { +public class StudySettingUpdateRequestDto { @NotNull(message = "lateThresholdMinutes는 필수입니다.") @Positive(message = "lateThresholdMiniutes는 0보다 커야합니다.") private Integer lateThresholdMinutes; diff --git a/src/main/resources/messages/messages-error.properties b/src/main/resources/messages/messages-error.properties index 36cc0ba..78d025c 100644 --- a/src/main/resources/messages/messages-error.properties +++ b/src/main/resources/messages/messages-error.properties @@ -131,4 +131,7 @@ user-study-setting.not-found=스터디에 등록된 유저를 찾을 수 없습 # banner banner.not-found = 배너를 찾을 수 없습니다. -banner.sort-order.not-proper = 올바르지 않은 배너 순서입니다. 순서는 전체 배너의 수와 같거나 작아야 하며, 1보다 같거나 커야 합니다. \ No newline at end of file +banner.sort-order.not-proper = 올바르지 않은 배너 순서입니다. 순서는 전체 배너의 수와 같거나 작아야 하며, 1보다 같거나 커야 합니다. + +#study-settings +study-settings.not-found=스터디 설정을 찾을 수 없습니다. \ No newline at end of file