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..cdea9b8 --- /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 createStudySetting(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..72db85a --- /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.createStudySetting(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..6932688 --- /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.StudySettingUpdateRequestDto; +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 StudySettingUpdateRequestDto requestDto + ) { + return studySettingService.update(studyId, requestDto.getLateThresholdMinutes(), requestDto.getAbsenceThresholdMinutes()); + } +} diff --git a/src/main/java/com/mos/backend/studysettings/presentation/requestdto/StudySettingUpdateRequestDto.java b/src/main/java/com/mos/backend/studysettings/presentation/requestdto/StudySettingUpdateRequestDto.java new file mode 100644 index 0000000..eae02ab --- /dev/null +++ b/src/main/java/com/mos/backend/studysettings/presentation/requestdto/StudySettingUpdateRequestDto.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 StudySettingUpdateRequestDto { + @NotNull(message = "lateThresholdMinutes는 필수입니다.") + @Positive(message = "lateThresholdMiniutes는 0보다 커야합니다.") + private Integer lateThresholdMinutes; + @NotNull(message = "absenceThresholdMinutes는 필수입니다.") + @Positive(message = "absenceThresholdMinutes는 0보다 커야합니다.") + private Integer absenceThresholdMinutes; +} 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