From d93fa183322e76666401fc84ab11775ec6274e05 Mon Sep 17 00:00:00 2001 From: shkisme Date: Mon, 17 Jul 2023 22:54:50 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=EC=84=B8=EB=AF=B8=EB=82=98=20?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=20=EC=BD=94=EB=93=9C=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?5=ED=9A=8C=20=EC=A0=9C=ED=95=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/SeminarAttendanceService.java | 21 +++++++++--- .../response/SeminarAttendanceResponse.java | 12 +++++-- .../homepage/global/error/ErrorCode.java | 1 + .../com/keeper/homepage/IntegrationTest.java | 8 +++-- .../api/SeminarAttendanceControllerTest.java | 3 +- .../application/SeminarServiceTest.java | 34 ++++++++++++++++++- 6 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java index e2415b5e5..f25973c0f 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java @@ -1,6 +1,7 @@ package com.keeper.homepage.domain.seminar.application; import static com.keeper.homepage.global.error.ErrorCode.MEMBER_NOT_FOUND; +import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_ATTENDANCE_ATTEMPT_NOT_AVAILABLE; import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_ATTENDANCE_CODE_NOT_AVAILABLE; import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_ATTENDANCE_DUPLICATE; import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_NOT_FOUND; @@ -15,6 +16,8 @@ import com.keeper.homepage.domain.seminar.entity.Seminar; import com.keeper.homepage.domain.seminar.entity.SeminarAttendance; import com.keeper.homepage.global.error.BusinessException; +import com.keeper.homepage.global.util.redis.RedisUtil; +import java.time.LocalDate; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,11 +27,15 @@ @Transactional(readOnly = true) public class SeminarAttendanceService { + private final RedisUtil redisUtil; private final SeminarAttendanceRepository attendanceRepository; private final SeminarRepository seminarRepository; @Transactional - public SeminarAttendanceResponse save(Long seminarId, Member member, SeminarAttendanceCodeRequest request){ + public SeminarAttendanceResponse save(Long seminarId, Member member, SeminarAttendanceCodeRequest request) { + String key = "seminar:" + LocalDate.now() + "memberId:" + member.getId(); + int attemptNumber = checkAttemptLimit(key); + Seminar seminar = seminarRepository.findById(seminarId) .orElseThrow(() -> new BusinessException(seminarId, "seminarId", SEMINAR_NOT_FOUND)); @@ -40,8 +47,15 @@ public SeminarAttendanceResponse save(Long seminarId, Member member, SeminarAtte .member(member) .seminarAttendanceStatus(seminar.getStatus()) .build(); + return SeminarAttendanceResponse.of(attendanceRepository.save(attendance), attemptNumber); + } - return SeminarAttendanceResponse.from(attendanceRepository.save(attendance)); + private int checkAttemptLimit(String key) { + int attemptNumber = redisUtil.increaseAndGetWithExpire(key, 60 * 60 * 3).intValue(); // 3시간 후 만료 + if (attemptNumber > 5) { + throw new BusinessException(attemptNumber, "attemptNumber", SEMINAR_ATTENDANCE_ATTEMPT_NOT_AVAILABLE); + } + return attemptNumber; } private void validAttendanceCode(Seminar seminar, SeminarAttendanceCodeRequest request) { @@ -49,8 +63,7 @@ private void validAttendanceCode(Seminar seminar, SeminarAttendanceCodeRequest r String inputAttendanceCode = request.attendanceCode(); if (!seminarAttendanceCode.equals(inputAttendanceCode)) { - throw new BusinessException(inputAttendanceCode, "inputAttendanceCode", - SEMINAR_ATTENDANCE_CODE_NOT_AVAILABLE); + throw new BusinessException(inputAttendanceCode, "inputAttendanceCode", SEMINAR_ATTENDANCE_CODE_NOT_AVAILABLE); } } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarAttendanceResponse.java b/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarAttendanceResponse.java index 55aab65de..e7341d110 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarAttendanceResponse.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarAttendanceResponse.java @@ -6,19 +6,25 @@ import com.keeper.homepage.domain.seminar.entity.SeminarAttendance; import com.keeper.homepage.domain.seminar.entity.SeminarAttendanceStatus.SeminarAttendanceStatusType; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter +@Builder @NoArgsConstructor(access = PRIVATE) @AllArgsConstructor(access = PACKAGE) public class SeminarAttendanceResponse { private Long id; private SeminarAttendanceStatusType statusType; + private int attemptNumber; - public static SeminarAttendanceResponse from(SeminarAttendance attendance) { - return new SeminarAttendanceResponse(attendance.getId(), - attendance.getSeminarAttendanceStatus().getType()); + public static SeminarAttendanceResponse of(SeminarAttendance attendance, int attemptNumber) { + return SeminarAttendanceResponse.builder() + .id(attendance.getId()) + .statusType(attendance.getSeminarAttendanceStatus().getType()) + .attemptNumber(attemptNumber) + .build(); } } diff --git a/src/main/java/com/keeper/homepage/global/error/ErrorCode.java b/src/main/java/com/keeper/homepage/global/error/ErrorCode.java index 251a23313..a0d715d98 100644 --- a/src/main/java/com/keeper/homepage/global/error/ErrorCode.java +++ b/src/main/java/com/keeper/homepage/global/error/ErrorCode.java @@ -29,6 +29,7 @@ public enum ErrorCode { SEMINAR_NOT_FOUND("해당 세미나를 찾을 수 없습니다.", HttpStatus.NOT_FOUND), SEMINAR_TIME_NOT_AVAILABLE("올바르지 않은 시간 값입니다", HttpStatus.BAD_REQUEST), SEMINAR_ATTENDANCE_CODE_NOT_AVAILABLE("올바르지 않은 출석 코드입니다.", HttpStatus.BAD_REQUEST), + SEMINAR_ATTENDANCE_ATTEMPT_NOT_AVAILABLE("출석코드 입력 가능 횟수를 초과하여 출석이 불가능합니다.", HttpStatus.BAD_REQUEST), SEMINAR_ATTENDANCE_DUPLICATE("이미 출석을 완료한 계정입니다.", HttpStatus.CONFLICT), // POST POST_CATEGORY_NOT_FOUND("존재하지 않는 게시글 카테고리입니다.", HttpStatus.NOT_FOUND), diff --git a/src/test/java/com/keeper/homepage/IntegrationTest.java b/src/test/java/com/keeper/homepage/IntegrationTest.java index 697ef7c7e..3c19d6ab5 100644 --- a/src/test/java/com/keeper/homepage/IntegrationTest.java +++ b/src/test/java/com/keeper/homepage/IntegrationTest.java @@ -64,6 +64,7 @@ import com.keeper.homepage.domain.post.dao.PostRepository; import com.keeper.homepage.domain.post.dao.category.CategoryRepository; import com.keeper.homepage.domain.seminar.SeminarTestHelper; +import com.keeper.homepage.domain.seminar.application.SeminarAttendanceService; import com.keeper.homepage.domain.seminar.application.SeminarService; import com.keeper.homepage.domain.seminar.application.convenience.ValidSeminarFindService; import com.keeper.homepage.domain.seminar.dao.SeminarAttendanceExcuseRepository; @@ -287,13 +288,16 @@ public class IntegrationTest { @SpykBean protected GameService gameService; - + @SpyBean protected MemberJobService memberJobService; - + @SpyBean protected AttendanceService attendanceService; + @SpyBean + protected SeminarAttendanceService seminarAttendanceService; + /******* Helper *******/ @SpyBean protected StaticWriteTestHelper staticWriteTestHelper; diff --git a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java index bcf064881..720c39509 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java @@ -83,7 +83,8 @@ public void should_success_attendanceSeminar() throws Exception { field("attendanceCode", "세미나 출석 코드")), responseFields( field("id", "세미나 출석 ID"), - field("statusType", "출석 상태")) + field("statusType", "출석 상태"), + field("attemptNumber", "출석 시도 횟수 (5회까지 가능합니다.)")) )).andReturn(); SeminarAttendanceStatusType statusType = objectMapper.readValue( diff --git a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java index 52e32c584..980ab1087 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java @@ -1,10 +1,42 @@ package com.keeper.homepage.domain.seminar.application; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import com.keeper.homepage.IntegrationTest; +import com.keeper.homepage.domain.member.entity.Member; +import com.keeper.homepage.domain.seminar.dto.request.SeminarAttendanceCodeRequest; +import com.keeper.homepage.domain.seminar.entity.Seminar; +import com.keeper.homepage.global.error.BusinessException; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class SeminarServiceTest extends IntegrationTest { + + @Nested + @DisplayName("출석코드 입력 제한 테스트") + class AttemptLimitTest { + + + @Test + @DisplayName("출석코드 5회 초과 입력 시 출석이 불가능해야 한다.") + public void 출석코드_5회_초과_입력_시_출석이_불가능해야_한다() throws Exception { + Seminar seminar = seminarTestHelper.generate(); + Member member = memberTestHelper.generate(); + + String invalidAttendanceCode = "12345"; + + for (int i = 0; i < 5; i++) { + assertThatThrownBy(() -> seminarAttendanceService.save(seminar.getId(), member, + new SeminarAttendanceCodeRequest(invalidAttendanceCode))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining("올바르지 않은 출석 코드입니다."); + } + + assertThatThrownBy(() -> seminarAttendanceService.save(seminar.getId(), member, + new SeminarAttendanceCodeRequest(seminar.getAttendanceCode()))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining("출석코드 입력 가능 횟수를 초과하여 출석이 불가능합니다."); + } + } } From 02f00552dc266e37f98a6c86b33bc192d309f989 Mon Sep 17 00:00:00 2001 From: shkisme Date: Wed, 19 Jul 2023 10:42:44 +0900 Subject: [PATCH 02/12] =?UTF-8?q?style:=20=EC=84=B8=EB=AF=B8=EB=82=98=20?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=8B=9C=ED=96=89=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=AA=85=20SeminarAt?= =?UTF-8?q?tendanceTest=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...eminarServiceTest.java => SeminarAttendanceServiceTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/keeper/homepage/domain/seminar/application/{SeminarServiceTest.java => SeminarAttendanceServiceTest.java} (96%) diff --git a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceServiceTest.java similarity index 96% rename from src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java rename to src/test/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceServiceTest.java index 980ab1087..819b97cbf 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceServiceTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -class SeminarServiceTest extends IntegrationTest { +class SeminarAttendanceServiceTest extends IntegrationTest { @Nested @DisplayName("출석코드 입력 제한 테스트") From 037e7dcd4b73c838cb395420f984907d42f1b636 Mon Sep 17 00:00:00 2001 From: shkisme Date: Thu, 20 Jul 2023 21:30:49 +0900 Subject: [PATCH 03/12] =?UTF-8?q?fix:=20attemtNumber=EB=A5=BC=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=EC=9D=91=EB=8B=B5=EC=9C=BC=EB=A1=9C=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EC=A7=80=20=EC=95=8A=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seminar/application/SeminarAttendanceService.java | 9 ++++----- .../seminar/dto/response/SeminarAttendanceResponse.java | 4 +--- .../com/keeper/homepage/global/util/redis/RedisUtil.java | 5 +++++ .../seminar/api/SeminarAttendanceControllerTest.java | 3 +-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java index f25973c0f..5349e8c7d 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java @@ -34,7 +34,7 @@ public class SeminarAttendanceService { @Transactional public SeminarAttendanceResponse save(Long seminarId, Member member, SeminarAttendanceCodeRequest request) { String key = "seminar:" + LocalDate.now() + "memberId:" + member.getId(); - int attemptNumber = checkAttemptLimit(key); + checkAttemptNumberLimit(key); Seminar seminar = seminarRepository.findById(seminarId) .orElseThrow(() -> new BusinessException(seminarId, "seminarId", SEMINAR_NOT_FOUND)); @@ -47,15 +47,14 @@ public SeminarAttendanceResponse save(Long seminarId, Member member, SeminarAtte .member(member) .seminarAttendanceStatus(seminar.getStatus()) .build(); - return SeminarAttendanceResponse.of(attendanceRepository.save(attendance), attemptNumber); + return SeminarAttendanceResponse.of(attendanceRepository.save(attendance)); } - private int checkAttemptLimit(String key) { - int attemptNumber = redisUtil.increaseAndGetWithExpire(key, 60 * 60 * 3).intValue(); // 3시간 후 만료 + private void checkAttemptNumberLimit(String key) { + int attemptNumber = redisUtil.increaseAndGetWithExpire(key, 60 * 60).intValue(); // 1시간 후 만료 if (attemptNumber > 5) { throw new BusinessException(attemptNumber, "attemptNumber", SEMINAR_ATTENDANCE_ATTEMPT_NOT_AVAILABLE); } - return attemptNumber; } private void validAttendanceCode(Seminar seminar, SeminarAttendanceCodeRequest request) { diff --git a/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarAttendanceResponse.java b/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarAttendanceResponse.java index e7341d110..2d02bdeb6 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarAttendanceResponse.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarAttendanceResponse.java @@ -18,13 +18,11 @@ public class SeminarAttendanceResponse { private Long id; private SeminarAttendanceStatusType statusType; - private int attemptNumber; - public static SeminarAttendanceResponse of(SeminarAttendance attendance, int attemptNumber) { + public static SeminarAttendanceResponse of(SeminarAttendance attendance) { return SeminarAttendanceResponse.builder() .id(attendance.getId()) .statusType(attendance.getSeminarAttendanceStatus().getType()) - .attemptNumber(attemptNumber) .build(); } } diff --git a/src/main/java/com/keeper/homepage/global/util/redis/RedisUtil.java b/src/main/java/com/keeper/homepage/global/util/redis/RedisUtil.java index 0b544eeb0..15ebefd14 100644 --- a/src/main/java/com/keeper/homepage/global/util/redis/RedisUtil.java +++ b/src/main/java/com/keeper/homepage/global/util/redis/RedisUtil.java @@ -47,6 +47,11 @@ public Long increaseAndGetWithExpire(String key, long durationMillis) { return valueOperations.increment(key); } + public Long increment(String key) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + return valueOperations.increment(key); + } + public void deleteData(String key) { redisTemplate.delete(key); } diff --git a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java index 720c39509..bcf064881 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java @@ -83,8 +83,7 @@ public void should_success_attendanceSeminar() throws Exception { field("attendanceCode", "세미나 출석 코드")), responseFields( field("id", "세미나 출석 ID"), - field("statusType", "출석 상태"), - field("attemptNumber", "출석 시도 횟수 (5회까지 가능합니다.)")) + field("statusType", "출석 상태")) )).andReturn(); SeminarAttendanceStatusType statusType = objectMapper.readValue( From 7e5079b9d432cdf1b092d7dfc0a40cabd01cbe15 Mon Sep 17 00:00:00 2001 From: shkisme Date: Fri, 21 Jul 2023 18:04:03 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=EA=B0=80=EC=9E=A5=20=EC=B5=9C?= =?UTF-8?q?=EA=B7=BC=EC=97=90=20=EB=A7=88=EA=B0=90=EB=90=9C=20=EC=84=B8?= =?UTF-8?q?=EB=AF=B8=EB=82=98=20=EC=A1=B0=ED=9A=8C=20api=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/seminar/seminar.adoc | 24 +++++ .../domain/seminar/api/SeminarController.java | 7 ++ .../seminar/application/SeminarService.java | 7 ++ .../domain/seminar/dao/SeminarRepository.java | 8 ++ .../seminar/api/SeminarControllerTest.java | 33 +++++++ .../application/SeminarServiceTest.java | 87 +++++++++++++++++++ 6 files changed, 166 insertions(+) create mode 100644 src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java diff --git a/src/docs/asciidoc/seminar/seminar.adoc b/src/docs/asciidoc/seminar/seminar.adoc index 90fa0b9f3..90fd7a519 100644 --- a/src/docs/asciidoc/seminar/seminar.adoc +++ b/src/docs/asciidoc/seminar/seminar.adoc @@ -179,6 +179,30 @@ include::{snippets}/delete-seminar/request-cookies.adoc[] include::{snippets}/delete-seminar/http-response.adoc[] +== *가장 최근에 마감된 세미나 ID 조회* + +NOTE: 어제까지의 세미나 중, 가장 현재와 가깝고 마감된 세미나의 ID를 조회합니다. + +=== 요청 + +==== Request + +include::{snippets}/get-recently-done-seminar-id/http-request.adoc[] + +==== Request Cookies + +include::{snippets}/get-recently-done-seminar-id/request-cookies.adoc[] + +=== 응답 + +==== Response + +include::{snippets}/get-recently-done-seminar-id/http-response.adoc[] + +==== Response Fields + +include::{snippets}/get-recently-done-seminar-id/response-fields.adoc[] + == *세미나 출석* link:seminar-attendance.html[API 문서 보기] diff --git a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java index a456356c2..d6592c67c 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java @@ -77,4 +77,11 @@ public ResponseEntity getSeminar(@PathVariable long seminarId) SeminarResponse response = seminarService.findById(seminarId); return ResponseEntity.status(HttpStatus.OK).body(response); } + + @GetMapping("/recently-done") + @Secured({"ROLE_회원"}) + public ResponseEntity getRecentlyDoneSeminarId() { + Long seminarId = seminarService.getRecentlyDoneSeminarId(); + return ResponseEntity.ok(new SeminarIdResponse(seminarId)); + } } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java index f87f0ce37..b90ac9426 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java @@ -106,4 +106,11 @@ public SeminarResponse findByDate(LocalDate localDate) { public SeminarListResponse findAll() { return new SeminarListResponse(validSeminarFindService.findAll()); } + + public Long getRecentlyDoneSeminarId() { + LocalDate now = LocalDate.now(); + return seminarRepository.findRecentlyDoneSeminar(now) + .map(Seminar::getId) + .orElse(null); + } } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java b/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java index d56a03d44..736472de6 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java @@ -21,4 +21,12 @@ public interface SeminarRepository extends JpaRepository { + "AND s.latenessCloseTime is not null " + "AND s.latenessCloseTime > :dateTime") Optional findByAvailable(@Param("dateTime") LocalDateTime dateTime); + + @Query("SELECT s FROM Seminar s " + + "WHERE s.id <> 1 " // virtual seminar data 제외 + + "AND DATE(s.openTime) < :localDate " + + "AND DATE(s.latenessCloseTime) < :localDate " + + "ORDER BY s.openTime DESC " + + "LIMIT 1") + Optional findRecentlyDoneSeminar(@Param("localDate") LocalDate localDate); } diff --git a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java index c9f535a79..49571330d 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java @@ -10,17 +10,20 @@ import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.keeper.homepage.domain.seminar.dto.request.SeminarStartRequest; import com.keeper.homepage.domain.seminar.dto.response.SeminarIdResponse; import com.keeper.homepage.domain.seminar.entity.Seminar; +import jakarta.servlet.http.Cookie; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -509,4 +512,34 @@ public void should_failDeleteNotExistsSeminar_when_admin() throws Exception { deleteSeminarUsingApi(adminToken, -1L).andExpect(status().isNotFound()); } } + + @Nested + @DisplayName("가장 최근에 마감된 세미나 조회 테스트") + class GetRecentlyDoneSeminarTest { + + @Test + @DisplayName("가장 최근에 마감된 세미나는 성공적으로 조회된다.") + public void 가장_최근에_마감된_세미나는_성공적으로_조회된다() throws Exception { + String securedValue = getSecuredValue(SeminarController.class, "getRecentlyDoneSeminarId"); + + seminarTestHelper.builder() + .openTime(LocalDateTime.now().minusDays(1)) + .latenessCloseTime(LocalDateTime.now().minusDays(1)) + .build(); + + em.flush(); + em.clear(); + + mockMvc.perform(get("/seminars/recently-done") + .cookie(new Cookie(ACCESS_TOKEN.getTokenName(), userToken))) + .andExpect(status().isOk()) + .andDo(document("get-recently-done-seminar-id", + requestCookies( + cookieWithName(ACCESS_TOKEN.getTokenName()) + .description("ACCESS TOKEN %s".formatted(securedValue)) + ), + responseFields(field("id", "세미나 ID")) + )); + } + } } diff --git a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java new file mode 100644 index 000000000..82bf444ec --- /dev/null +++ b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java @@ -0,0 +1,87 @@ +package com.keeper.homepage.domain.seminar.application; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.keeper.homepage.IntegrationTest; +import com.keeper.homepage.domain.seminar.dto.response.SeminarResponse; +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class SeminarServiceTest extends IntegrationTest { + + @Nested + @DisplayName("가장 최근에 마감된 세미나 조회 테스트") + class GetRecentlyDoneSeminarTest { + + + @Test + @DisplayName("지각 마감시간이 지난 세미나를 불러와야 한다.") + public void 지각_마감시간이_지난_세미나를_불러와야_한다() throws Exception { + //given + Long ongoingSeminarId = seminarTestHelper.builder() + .openTime(LocalDateTime.now().minusDays(2)) + .latenessCloseTime(LocalDateTime.now().plusDays(1)) + .build() + .getId(); + Long doneSeminarId = seminarTestHelper.builder() + .openTime(LocalDateTime.now().minusDays(1)) + .latenessCloseTime(LocalDateTime.now().minusDays(1)) + .build() + .getId(); + + //when + Long seminarId = seminarService.getRecentlyDoneSeminarId(); + + //then + assertThat(seminarId).isEqualTo(doneSeminarId); + } + + @Test + @DisplayName("오늘 날짜의 세미나는 불러오면 안된다.") + public void 오늘_날짜의_세미나는_불러오면_안된다() throws Exception { + //given + Long yesterdaySeminarId = seminarTestHelper.builder() + .openTime(LocalDateTime.now().minusDays(1)) + .latenessCloseTime(LocalDateTime.now().minusDays(1)) + .build() + .getId(); + Long todaySeminarId = seminarTestHelper.builder(). + openTime(LocalDateTime.now()) + .latenessCloseTime(LocalDateTime.now()) + .build() + .getId(); + + //when + Long seminarId = seminarService.getRecentlyDoneSeminarId(); + + //then + assertThat(seminarId).isEqualTo(yesterdaySeminarId); + } + + @Test + @DisplayName("마감된 세미나 중 시작시간 기준으로 현재와 가장 가까운 세미나를 불러와야 한다.") + public void 마감된_세미나_중_시작시간_기준으로_현재와_가장_가까운_세미나를_불러와야_한다() throws Exception { + seminarTestHelper.builder(). + openTime(LocalDateTime.now().minusDays(2)) + .latenessCloseTime(LocalDateTime.now().minusDays(2)) + .build(); + + Long recentSeminarId = seminarTestHelper.builder(). + openTime(LocalDateTime.now().minusDays(1)) + .latenessCloseTime(LocalDateTime.now().minusDays(1)) + .build() + .getId(); + + seminarTestHelper.builder(). + openTime(LocalDateTime.now().minusDays(3)) + .latenessCloseTime(LocalDateTime.now().minusDays(3)) + .build(); + + Long seminarId = seminarService.getRecentlyDoneSeminarId(); + + assertThat(seminarId).isEqualTo(recentSeminarId); + } + } +} From 697f04be3d2ff13bb8eed97f99f2a54777a2a9a6 Mon Sep 17 00:00:00 2001 From: shkisme Date: Fri, 21 Jul 2023 23:30:33 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=EC=98=88=EC=A0=95=EB=90=9C=20?= =?UTF-8?q?=EC=84=B8=EB=AF=B8=EB=82=98=20=EC=A4=91=20=EA=B0=80=EC=9E=A5=20?= =?UTF-8?q?=EC=B5=9C=EA=B7=BC=EC=9D=98=20=EC=84=B8=EB=AF=B8=EB=82=98=202?= =?UTF-8?q?=EA=B0=9C=EB=A5=BC=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20api?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/seminar/seminar.adoc | 36 ++++++++++-- .../domain/seminar/api/SeminarController.java | 14 ++++- .../seminar/application/SeminarService.java | 13 ++++- .../domain/seminar/dao/SeminarRepository.java | 7 +++ .../seminar/api/SeminarControllerTest.java | 35 +++++++++++- .../application/SeminarServiceTest.java | 55 ++++++++++++++----- 6 files changed, 134 insertions(+), 26 deletions(-) diff --git a/src/docs/asciidoc/seminar/seminar.adoc b/src/docs/asciidoc/seminar/seminar.adoc index 90fd7a519..28692b3f0 100644 --- a/src/docs/asciidoc/seminar/seminar.adoc +++ b/src/docs/asciidoc/seminar/seminar.adoc @@ -179,29 +179,53 @@ include::{snippets}/delete-seminar/request-cookies.adoc[] include::{snippets}/delete-seminar/http-response.adoc[] -== *가장 최근에 마감된 세미나 ID 조회* +== *가장 최근에 마감된 세미나 조회* -NOTE: 어제까지의 세미나 중, 가장 현재와 가깝고 마감된 세미나의 ID를 조회합니다. +NOTE: 어제까지의 세미나 중, 가장 현재와 가깝고 마감된 세미나를 조회합니다. === 요청 ==== Request -include::{snippets}/get-recently-done-seminar-id/http-request.adoc[] +include::{snippets}/get-recently-done-seminar/http-request.adoc[] ==== Request Cookies -include::{snippets}/get-recently-done-seminar-id/request-cookies.adoc[] +include::{snippets}/get-recently-done-seminar/request-cookies.adoc[] === 응답 ==== Response -include::{snippets}/get-recently-done-seminar-id/http-response.adoc[] +include::{snippets}/get-recently-done-seminar/http-response.adoc[] ==== Response Fields -include::{snippets}/get-recently-done-seminar-id/response-fields.adoc[] +include::{snippets}/get-recently-done-seminar/response-fields.adoc[] + +== *가장 최근의 예정된 세미나 2개 조회* + +NOTE: 오늘을 포함하여 가장 최근의 예정된 세미나 2개를 조회합니다. + +=== 요청 + +==== Request + +include::{snippets}/get-recently-upcoming-seminars/http-request.adoc[] + +==== Request Cookies + +include::{snippets}/get-recently-upcoming-seminars/request-cookies.adoc[] + +=== 응답 + +==== Response + +include::{snippets}/get-recently-upcoming-seminars/http-response.adoc[] + +==== Response Fields + +include::{snippets}/get-recently-upcoming-seminars/response-fields.adoc[] == *세미나 출석* diff --git a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java index d6592c67c..2d2c6e741 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java @@ -8,6 +8,7 @@ import com.keeper.homepage.domain.seminar.dto.response.SeminarResponse; import jakarta.validation.Valid; import java.time.LocalDate; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; @@ -80,8 +81,15 @@ public ResponseEntity getSeminar(@PathVariable long seminarId) @GetMapping("/recently-done") @Secured({"ROLE_회원"}) - public ResponseEntity getRecentlyDoneSeminarId() { - Long seminarId = seminarService.getRecentlyDoneSeminarId(); - return ResponseEntity.ok(new SeminarIdResponse(seminarId)); + public ResponseEntity getRecentlyDoneSeminar() { + SeminarIdResponse response = seminarService.getRecentlyDoneSeminar(); + return ResponseEntity.ok(response); + } + + @GetMapping("/recently-upcoming") + @Secured({"ROLE_회원"}) + public ResponseEntity> getRecentlyUpcomingSeminars() { + List responses = seminarService.getRecentlyUpcomingSeminars(); + return ResponseEntity.ok(responses); } } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java index b90ac9426..278e889e7 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java @@ -16,6 +16,7 @@ import com.keeper.homepage.global.error.ErrorCode; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; import java.util.Random; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -107,10 +108,20 @@ public SeminarListResponse findAll() { return new SeminarListResponse(validSeminarFindService.findAll()); } - public Long getRecentlyDoneSeminarId() { + public SeminarIdResponse getRecentlyDoneSeminar() { LocalDate now = LocalDate.now(); return seminarRepository.findRecentlyDoneSeminar(now) .map(Seminar::getId) + .map(SeminarIdResponse::new) .orElse(null); } + + public List getRecentlyUpcomingSeminars() { + LocalDate now = LocalDate.now(); + return seminarRepository.findRecentlyUpcomingSeminar(now) + .stream() + .map(Seminar::getId) + .map(SeminarIdResponse::new) + .toList(); + } } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java b/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java index 736472de6..1ad7f630f 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java @@ -29,4 +29,11 @@ public interface SeminarRepository extends JpaRepository { + "ORDER BY s.openTime DESC " + "LIMIT 1") Optional findRecentlyDoneSeminar(@Param("localDate") LocalDate localDate); + + @Query("SELECT s FROM Seminar s " + + "WHERE s.id <> 1 " // virtual seminar data 제외" + + "AND DATE(s.openTime) >= :localDate " + + "ORDER BY s.openTime ASC " + + "LIMIT 2") + List findRecentlyUpcomingSeminar(@Param("localDate") LocalDate localDate); } diff --git a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java index 49571330d..71d517f67 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java @@ -520,7 +520,7 @@ class GetRecentlyDoneSeminarTest { @Test @DisplayName("가장 최근에 마감된 세미나는 성공적으로 조회된다.") public void 가장_최근에_마감된_세미나는_성공적으로_조회된다() throws Exception { - String securedValue = getSecuredValue(SeminarController.class, "getRecentlyDoneSeminarId"); + String securedValue = getSecuredValue(SeminarController.class, "getRecentlyDoneSeminar"); seminarTestHelper.builder() .openTime(LocalDateTime.now().minusDays(1)) @@ -533,7 +533,7 @@ class GetRecentlyDoneSeminarTest { mockMvc.perform(get("/seminars/recently-done") .cookie(new Cookie(ACCESS_TOKEN.getTokenName(), userToken))) .andExpect(status().isOk()) - .andDo(document("get-recently-done-seminar-id", + .andDo(document("get-recently-done-seminar", requestCookies( cookieWithName(ACCESS_TOKEN.getTokenName()) .description("ACCESS TOKEN %s".formatted(securedValue)) @@ -542,4 +542,35 @@ class GetRecentlyDoneSeminarTest { )); } } + + @Nested + @DisplayName("가장 최근의 예정된 세미나 조회 테스트") + class GetRecentlyUpcomingSeminarTest { + + @Test + @DisplayName("유효한 요청일 경우 가장 최근의 예정된 세미나는 성공적으로 조회된다.") + public void 유효한_요청일_경우_가장_최근의_예정된_세미나는_성공적으로_조회된다() throws Exception { + String securedValue = getSecuredValue(SeminarController.class, "getRecentlyUpcomingSeminars"); + + seminarTestHelper.builder() + .openTime(LocalDateTime.now()) + .build(); + seminarTestHelper.builder() + .openTime(LocalDateTime.now().plusDays(1)) + .build(); + em.flush(); + em.clear(); + + mockMvc.perform(get("/seminars/recently-upcoming") + .cookie(new Cookie(ACCESS_TOKEN.getTokenName(), userToken))) + .andExpect(status().isOk()) + .andDo(document("get-recently-upcoming-seminars", + requestCookies( + cookieWithName(ACCESS_TOKEN.getTokenName()) + .description("ACCESS TOKEN %s".formatted(securedValue)) + ), + responseFields(field("[].id", "세미나 ID")) + )); + } + } } diff --git a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java index 82bf444ec..f5420353c 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarServiceTest.java @@ -3,8 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import com.keeper.homepage.IntegrationTest; -import com.keeper.homepage.domain.seminar.dto.response.SeminarResponse; +import com.keeper.homepage.domain.seminar.dto.response.SeminarIdResponse; import java.time.LocalDateTime; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -32,7 +33,7 @@ class GetRecentlyDoneSeminarTest { .getId(); //when - Long seminarId = seminarService.getRecentlyDoneSeminarId(); + Long seminarId = seminarService.getRecentlyDoneSeminar().id(); //then assertThat(seminarId).isEqualTo(doneSeminarId); @@ -47,14 +48,14 @@ class GetRecentlyDoneSeminarTest { .latenessCloseTime(LocalDateTime.now().minusDays(1)) .build() .getId(); - Long todaySeminarId = seminarTestHelper.builder(). - openTime(LocalDateTime.now()) + Long todaySeminarId = seminarTestHelper.builder() + .openTime(LocalDateTime.now()) .latenessCloseTime(LocalDateTime.now()) .build() .getId(); //when - Long seminarId = seminarService.getRecentlyDoneSeminarId(); + Long seminarId = seminarService.getRecentlyDoneSeminar().id(); //then assertThat(seminarId).isEqualTo(yesterdaySeminarId); @@ -63,25 +64,51 @@ class GetRecentlyDoneSeminarTest { @Test @DisplayName("마감된 세미나 중 시작시간 기준으로 현재와 가장 가까운 세미나를 불러와야 한다.") public void 마감된_세미나_중_시작시간_기준으로_현재와_가장_가까운_세미나를_불러와야_한다() throws Exception { - seminarTestHelper.builder(). - openTime(LocalDateTime.now().minusDays(2)) + seminarTestHelper.builder() + .openTime(LocalDateTime.now().minusDays(2)) .latenessCloseTime(LocalDateTime.now().minusDays(2)) .build(); - - Long recentSeminarId = seminarTestHelper.builder(). - openTime(LocalDateTime.now().minusDays(1)) + Long recentSeminarId = seminarTestHelper.builder() + .openTime(LocalDateTime.now().minusDays(1)) .latenessCloseTime(LocalDateTime.now().minusDays(1)) .build() .getId(); - - seminarTestHelper.builder(). - openTime(LocalDateTime.now().minusDays(3)) + seminarTestHelper.builder() + .openTime(LocalDateTime.now().minusDays(3)) .latenessCloseTime(LocalDateTime.now().minusDays(3)) .build(); - Long seminarId = seminarService.getRecentlyDoneSeminarId(); + Long seminarId = seminarService.getRecentlyDoneSeminar().id(); assertThat(seminarId).isEqualTo(recentSeminarId); } } + + @Nested + @DisplayName("가장 최근의 예정된 세미나 조회 테스트") + class GetRecentlyUpcomingSeminarTest { + + @Test + @DisplayName("오늘을 포함하여 예정된 최신 세미나 2개를 조회해야 한다.") + public void 오늘을_포함하여_예정된_최신_세미나_2개를_조회해야_한다() throws Exception { + Long yesterdaySeminarId = seminarTestHelper.builder() + .openTime(LocalDateTime.now().minusDays(1)) + .build() + .getId(); + Long seminarId1 = seminarTestHelper.builder() + .openTime(LocalDateTime.now()) + .build() + .getId(); + Long seminarId2 = seminarTestHelper.builder() + .openTime(LocalDateTime.now().plusDays(1)) + .build() + .getId(); + + List recentlyUpcomingSeminars = seminarService.getRecentlyUpcomingSeminars(); + + assertThat(recentlyUpcomingSeminars).hasSize(2); + assertThat(recentlyUpcomingSeminars.get(0).id()).isEqualTo(seminarId1); + assertThat(recentlyUpcomingSeminars.get(1).id()).isEqualTo(seminarId2); + } + } } From 52ae941f2590d124b880e7136df9f93fbdbf596c Mon Sep 17 00:00:00 2001 From: shkisme Date: Sun, 23 Jul 2023 16:38:29 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20MemberFindService.findAllRegular(?= =?UTF-8?q?)=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80=20(?= =?UTF-8?q?=EC=A0=95=ED=9A=8C=EC=9B=90=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/convenience/MemberFindService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/keeper/homepage/domain/member/application/convenience/MemberFindService.java b/src/main/java/com/keeper/homepage/domain/member/application/convenience/MemberFindService.java index f1d1bbaba..0f532987f 100644 --- a/src/main/java/com/keeper/homepage/domain/member/application/convenience/MemberFindService.java +++ b/src/main/java/com/keeper/homepage/domain/member/application/convenience/MemberFindService.java @@ -1,11 +1,14 @@ package com.keeper.homepage.domain.member.application.convenience; +import static com.keeper.homepage.domain.member.entity.type.MemberType.MemberTypeEnum.정회원; +import static com.keeper.homepage.domain.member.entity.type.MemberType.getMemberTypeBy; import static com.keeper.homepage.global.error.ErrorCode.MEMBER_NOT_FOUND; import com.keeper.homepage.domain.member.dao.MemberRepository; import com.keeper.homepage.domain.member.entity.Member; import com.keeper.homepage.domain.member.entity.embedded.RealName; import com.keeper.homepage.global.error.BusinessException; +import java.util.List; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -38,4 +41,11 @@ public Stream findAllByRealName(RealName realName) { return memberRepository.findAllByProfileRealNameAndIdNot(realName, VIRTUAL_MEMBER_ID) .stream(); } + + public List findAllRegular() { + return memberRepository.findAllByMemberType(getMemberTypeBy(정회원)) + .stream() + .filter(member -> member.getId() != VIRTUAL_MEMBER_ID) + .toList(); + } } From 3d34ae6c47e1d049d004d96d034e4a08b383cbe7 Mon Sep 17 00:00:00 2001 From: shkisme Date: Sun, 23 Jul 2023 16:43:54 +0900 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20=EC=84=B8=EB=AF=B8=EB=82=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=AA=A8=EB=93=A0=20=EC=A0=95?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EC=9D=98=20=EC=B6=9C=EC=84=9D=EC=9D=84=20`?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=20=EC=A0=84`=20=EC=83=81=ED=83=9C=EB=A1=9C?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20-=20=EC=84=B8=EB=AF=B8=EB=82=98=20?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=EC=9D=80=20=EC=A0=95=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EB=A7=8C=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=A8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/seminar/application/SeminarService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java index 278e889e7..d04530591 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java @@ -30,13 +30,25 @@ public class SeminarService { private static final Random RANDOM = new Random(); private final SeminarRepository seminarRepository; + private final SeminarAttendanceRepository seminarAttendanceRepository; private final ValidSeminarFindService validSeminarFindService; + private final MemberFindService memberFindService; @Transactional public SeminarIdResponse save() { Seminar seminar = Seminar.builder() .attendanceCode(randomAttendanceCode()) .build(); + + List activeMembers = memberFindService.findAllRegular(); + activeMembers.forEach(member -> { + seminarAttendanceRepository.save( + SeminarAttendance.builder() + .seminar(seminar) + .member(member) + .seminarAttendanceStatus(getSeminarAttendanceStatusBy(BEFORE_ATTENDANCE)) + .build()); + }); return new SeminarIdResponse(seminarRepository.save(seminar).getId()); } From 249f978cad14a2fb4e74ba57f3fa8424a3e96e4a Mon Sep 17 00:00:00 2001 From: shkisme Date: Sun, 23 Jul 2023 16:59:14 +0900 Subject: [PATCH 08/12] =?UTF-8?q?fix:=20=EC=84=B8=EB=AF=B8=EB=82=98=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20api=20=EC=88=98=EC=A0=95=20-=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EB=8F=84=20=EC=A0=91=EA=B7=BC=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20-=20=EC=9E=90=EC=8B=A0=EC=9D=98?= =?UTF-8?q?=20=EC=B6=9C=EC=84=9D=20=EC=83=81=ED=83=9C=EB=8F=84=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=9C=BC=EB=A1=9C=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20(=EB=A7=8C=EC=95=BD=20=EC=B6=9C=EC=84=9D?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=97=86=EC=9D=84=20=EC=8B=9C?= =?UTF-8?q?=EB=8A=94=20`=EC=B6=9C=EC=84=9D=20=EC=A0=84`=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A5=BC=20=EB=B0=98=ED=99=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/seminar/api/SeminarController.java | 11 ++++-- .../seminar/application/SeminarService.java | 27 +++++++++----- .../convenience/ValidSeminarFindService.java | 9 +++++ .../domain/seminar/dao/SeminarRepository.java | 2 ++ .../dto/response/SeminarDetailResponse.java | 35 +++++++++++++++++++ 5 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarDetailResponse.java diff --git a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java index 2d2c6e741..77ead891c 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarController.java @@ -1,11 +1,14 @@ package com.keeper.homepage.domain.seminar.api; +import com.keeper.homepage.domain.member.entity.Member; import com.keeper.homepage.domain.seminar.application.SeminarService; import com.keeper.homepage.domain.seminar.dto.request.SeminarStartRequest; import com.keeper.homepage.domain.seminar.dto.response.SeminarAttendanceCodeResponse; +import com.keeper.homepage.domain.seminar.dto.response.SeminarDetailResponse; import com.keeper.homepage.domain.seminar.dto.response.SeminarIdResponse; import com.keeper.homepage.domain.seminar.dto.response.SeminarListResponse; import com.keeper.homepage.domain.seminar.dto.response.SeminarResponse; +import com.keeper.homepage.global.config.security.annotation.LoginMember; import jakarta.validation.Valid; import java.time.LocalDate; import java.util.List; @@ -74,8 +77,12 @@ public ResponseEntity getSeminarByDate( } @GetMapping("/{seminarId}") - public ResponseEntity getSeminar(@PathVariable long seminarId) { - SeminarResponse response = seminarService.findById(seminarId); + @Secured({"ROLE_회원"}) + public ResponseEntity getSeminar( + @LoginMember Member member, + @PathVariable long seminarId + ) { + SeminarDetailResponse response = seminarService.findById(member, seminarId); return ResponseEntity.status(HttpStatus.OK).body(response); } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java index d04530591..c04abd938 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarService.java @@ -1,17 +1,23 @@ package com.keeper.homepage.domain.seminar.application; -import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_NOT_FOUND; +import static com.keeper.homepage.domain.seminar.entity.SeminarAttendanceStatus.SeminarAttendanceStatusType.BEFORE_ATTENDANCE; +import static com.keeper.homepage.domain.seminar.entity.SeminarAttendanceStatus.getSeminarAttendanceStatusBy; import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_TIME_NOT_AVAILABLE; import static java.util.stream.Collectors.joining; +import com.keeper.homepage.domain.member.application.convenience.MemberFindService; +import com.keeper.homepage.domain.member.entity.Member; import com.keeper.homepage.domain.seminar.application.convenience.ValidSeminarFindService; +import com.keeper.homepage.domain.seminar.dao.SeminarAttendanceRepository; import com.keeper.homepage.domain.seminar.dao.SeminarRepository; import com.keeper.homepage.domain.seminar.dto.request.SeminarStartRequest; import com.keeper.homepage.domain.seminar.dto.response.SeminarAttendanceCodeResponse; +import com.keeper.homepage.domain.seminar.dto.response.SeminarDetailResponse; import com.keeper.homepage.domain.seminar.dto.response.SeminarIdResponse; import com.keeper.homepage.domain.seminar.dto.response.SeminarListResponse; import com.keeper.homepage.domain.seminar.dto.response.SeminarResponse; import com.keeper.homepage.domain.seminar.entity.Seminar; +import com.keeper.homepage.domain.seminar.entity.SeminarAttendance; import com.keeper.homepage.global.error.BusinessException; import com.keeper.homepage.global.error.ErrorCode; import java.time.LocalDate; @@ -64,8 +70,7 @@ private String randomAttendanceCode() { public SeminarAttendanceCodeResponse start(Long seminarId, SeminarStartRequest request) { validCloseTime(request); - Seminar seminar = seminarRepository.findById(seminarId) - .orElseThrow(() -> new BusinessException(seminarId, "seminarId", SEMINAR_NOT_FOUND)); + Seminar seminar = validSeminarFindService.findById(seminarId); seminar.changeCloseTime(request.attendanceCloseTime(), request.latenessCloseTime()); return new SeminarAttendanceCodeResponse(seminar.getAttendanceCode()); } @@ -95,14 +100,20 @@ public static void requireNonNull(T obj, String fieldName, ErrorCode errorCo @Transactional public void delete(long seminarId) { - Seminar seminar = seminarRepository.findById(seminarId) - .orElseThrow(() -> new BusinessException(seminarId, "seminarId", SEMINAR_NOT_FOUND)); + Seminar seminar = validSeminarFindService.findById(seminarId); seminarRepository.delete(seminar); } - public SeminarResponse findById(long seminarId) { - return SeminarResponse.from(seminarRepository.findById(seminarId) - .orElseThrow(() -> new BusinessException(seminarId, "seminarId", SEMINAR_NOT_FOUND))); + public SeminarDetailResponse findById(Member member, long seminarId) { + Seminar seminar = validSeminarFindService.findById(seminarId); + + var seminarAttendanceStatusType = seminarAttendanceRepository.findBySeminarAndMember(seminar, member) + .orElse(SeminarAttendance.builder() + .seminarAttendanceStatus(getSeminarAttendanceStatusBy(BEFORE_ATTENDANCE)) + .build()) + .getSeminarAttendanceStatus() + .getType(); + return SeminarDetailResponse.from(seminar, seminarAttendanceStatusType); } public SeminarResponse findByAvailable() { diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/convenience/ValidSeminarFindService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/convenience/ValidSeminarFindService.java index bcac5f5c0..91cc4f384 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/convenience/ValidSeminarFindService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/convenience/ValidSeminarFindService.java @@ -1,7 +1,11 @@ package com.keeper.homepage.domain.seminar.application.convenience; +import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_NOT_FOUND; + import com.keeper.homepage.domain.seminar.dao.SeminarRepository; import com.keeper.homepage.domain.seminar.dto.response.SeminarResponse; +import com.keeper.homepage.domain.seminar.entity.Seminar; +import com.keeper.homepage.global.error.BusinessException; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -20,4 +24,9 @@ public List findAll() { .map(SeminarResponse::from) .toList(); } + + public Seminar findById(long seminarId) { + return seminarRepository.findByIdAndIdNot(seminarId, VIRTUAL_SEMINAR_ID) + .orElseThrow(() -> new BusinessException(seminarId, "seminarId", SEMINAR_NOT_FOUND)); + } } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java b/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java index 1ad7f630f..9646c75b8 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/dao/SeminarRepository.java @@ -13,6 +13,8 @@ public interface SeminarRepository extends JpaRepository { List findAllByIdIsNot(Long seminarId); + Optional findByIdAndIdNot(Long seminarId, Long virtualId); + @Query("SELECT s FROM Seminar s WHERE DATE(s.openTime) =:localDate") Optional findByOpenTime(@Param("localDate") LocalDate localDate); diff --git a/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarDetailResponse.java b/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarDetailResponse.java new file mode 100644 index 000000000..28773b24e --- /dev/null +++ b/src/main/java/com/keeper/homepage/domain/seminar/dto/response/SeminarDetailResponse.java @@ -0,0 +1,35 @@ +package com.keeper.homepage.domain.seminar.dto.response; + +import com.keeper.homepage.domain.seminar.entity.Seminar; +import com.keeper.homepage.domain.seminar.entity.SeminarAttendance; +import com.keeper.homepage.domain.seminar.entity.SeminarAttendanceStatus; +import com.keeper.homepage.domain.seminar.entity.SeminarAttendanceStatus.SeminarAttendanceStatusType; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class SeminarDetailResponse { + + private Long seminarId; + private String seminarName; + private LocalDateTime openTime; + private LocalDateTime attendanceCloseTime; + private LocalDateTime latenessCloseTime; + private String statusType; + + public static SeminarDetailResponse from(Seminar seminar, SeminarAttendanceStatusType seminarAttendanceStatusType) { + return SeminarDetailResponse.builder() + .seminarId(seminar.getId()) + .seminarName(seminar.getName()) + .openTime(seminar.getOpenTime()) + .attendanceCloseTime(seminar.getAttendanceCloseTime()) + .latenessCloseTime(seminar.getLatenessCloseTime()) + .statusType(seminarAttendanceStatusType.getType()) + .build(); + } +} From 64f708fd96ecb3fb0223bfa4aeb44f4d95c992fc Mon Sep 17 00:00:00 2001 From: shkisme Date: Sun, 23 Jul 2023 17:04:38 +0900 Subject: [PATCH 09/12] =?UTF-8?q?fix:=20=EC=84=B8=EB=AF=B8=EB=82=98=20?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=20api=20=EC=88=98=EC=A0=95=20-=20=EC=B6=9C?= =?UTF-8?q?=EC=84=9D=20api=EC=9D=98=20HTTP=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20@PatchMapping=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20-=20=ED=8B=80=EB=A6=B0=20=EC=B6=9C=EC=84=9D=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=85=EB=A0=A5=ED=95=A0=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80?= =?UTF-8?q?=EC=99=80=20=ED=95=A8=EA=BB=98=20=EC=8B=9C=EB=8F=84=20=ED=9A=9F?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20-=20=EC=B6=9C=EC=84=9D=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A5=BC=20=EC=B0=BE=EC=9D=84=20=EC=88=98=20=EC=97=86=EB=8A=94?= =?UTF-8?q?=20=EA=B2=BD=EC=9A=B0=20=EC=97=90=EB=9F=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/dao/MemberRepository.java | 3 ++ .../api/SeminarAttendanceController.java | 5 +- .../application/SeminarAttendanceService.java | 52 ++++++++++--------- .../homepage/global/error/ErrorCode.java | 1 + 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/keeper/homepage/domain/member/dao/MemberRepository.java b/src/main/java/com/keeper/homepage/domain/member/dao/MemberRepository.java index 3562c91a0..d0fdbe1e4 100644 --- a/src/main/java/com/keeper/homepage/domain/member/dao/MemberRepository.java +++ b/src/main/java/com/keeper/homepage/domain/member/dao/MemberRepository.java @@ -5,6 +5,7 @@ import com.keeper.homepage.domain.member.entity.embedded.LoginId; import com.keeper.homepage.domain.member.entity.embedded.RealName; import com.keeper.homepage.domain.member.entity.embedded.StudentId; +import com.keeper.homepage.domain.member.entity.type.MemberType; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; @@ -35,4 +36,6 @@ Optional findByProfileEmailAddressAndProfileLoginId(EmailAddress profile List findAllByProfileRealNameAndIdNot(RealName realName, long virtualId); void deleteAllByIdNot(Long virtualId); + + List findAllByMemberType(MemberType type); } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceController.java b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceController.java index 5276328fd..f70388321 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceController.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceController.java @@ -12,7 +12,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -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; @@ -24,12 +23,12 @@ public class SeminarAttendanceController { private final SeminarAttendanceService seminarAttendanceService; - @PostMapping + @PatchMapping public ResponseEntity attendanceSeminar( @PathVariable Long seminarId, @LoginMember Member member, @RequestBody @Valid SeminarAttendanceCodeRequest request) { - SeminarAttendanceResponse response = seminarAttendanceService.save(seminarId, member, request); + SeminarAttendanceResponse response = seminarAttendanceService.attendance(seminarId, member, request); return ResponseEntity.status(HttpStatus.CREATED).body(response); } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java index 5349e8c7d..787bfae9f 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java @@ -1,15 +1,16 @@ package com.keeper.homepage.domain.seminar.application; +import static com.keeper.homepage.domain.seminar.entity.SeminarAttendanceStatus.SeminarAttendanceStatusType.BEFORE_ATTENDANCE; import static com.keeper.homepage.global.error.ErrorCode.MEMBER_NOT_FOUND; import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_ATTENDANCE_ATTEMPT_NOT_AVAILABLE; import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_ATTENDANCE_CODE_NOT_AVAILABLE; import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_ATTENDANCE_DUPLICATE; -import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_NOT_FOUND; +import static com.keeper.homepage.global.error.ErrorCode.SEMINAR_ATTENDANCE_UNABLE; +import com.keeper.homepage.domain.member.application.convenience.MemberFindService; import com.keeper.homepage.domain.member.entity.Member; -import com.keeper.homepage.domain.member.entity.embedded.Nickname; +import com.keeper.homepage.domain.seminar.application.convenience.ValidSeminarFindService; import com.keeper.homepage.domain.seminar.dao.SeminarAttendanceRepository; -import com.keeper.homepage.domain.seminar.dao.SeminarRepository; import com.keeper.homepage.domain.seminar.dto.request.SeminarAttendanceCodeRequest; import com.keeper.homepage.domain.seminar.dto.request.SeminarAttendanceStatusRequest; import com.keeper.homepage.domain.seminar.dto.response.SeminarAttendanceResponse; @@ -28,47 +29,50 @@ public class SeminarAttendanceService { private final RedisUtil redisUtil; + private final ValidSeminarFindService validSeminarFindService; + private final MemberFindService memberFindService; private final SeminarAttendanceRepository attendanceRepository; - private final SeminarRepository seminarRepository; + + private static final int MAX_ATTEMPT_COUNT = 5; + private static final int ONE_HOUR = 60 * 60; @Transactional - public SeminarAttendanceResponse save(Long seminarId, Member member, SeminarAttendanceCodeRequest request) { - String key = "seminar:" + LocalDate.now() + "memberId:" + member.getId(); - checkAttemptNumberLimit(key); + public SeminarAttendanceResponse attendance(Long seminarId, Member member, SeminarAttendanceCodeRequest request) { + Seminar seminar = validSeminarFindService.findById(seminarId); + SeminarAttendance seminarAttendance = attendanceRepository.findBySeminarAndMember(seminar, member) + .orElseThrow(() -> new BusinessException(member.getRealName(), "member", SEMINAR_ATTENDANCE_UNABLE)); - Seminar seminar = seminarRepository.findById(seminarId) - .orElseThrow(() -> new BusinessException(seminarId, "seminarId", SEMINAR_NOT_FOUND)); + String key = "seminar:" + LocalDate.now() + ":memberId:" + member.getId(); + checkAttemptNumberLimit(key); - validAttendanceCode(seminar, request); - checkExistsAttendanceSeminar(seminar, member); + validAttendanceCode(seminar, request, key); + checkDuplicateAttendance(seminarAttendance); - SeminarAttendance attendance = SeminarAttendance.builder() - .seminar(seminar) - .member(member) - .seminarAttendanceStatus(seminar.getStatus()) - .build(); - return SeminarAttendanceResponse.of(attendanceRepository.save(attendance)); + seminarAttendance.changeStatus(seminar.getStatus().getType()); + return SeminarAttendanceResponse.of(seminarAttendance); } private void checkAttemptNumberLimit(String key) { - int attemptNumber = redisUtil.increaseAndGetWithExpire(key, 60 * 60).intValue(); // 1시간 후 만료 - if (attemptNumber > 5) { + int attemptNumber = redisUtil.increaseAndGetWithExpire(key, ONE_HOUR).intValue(); + if (attemptNumber > MAX_ATTEMPT_COUNT) { throw new BusinessException(attemptNumber, "attemptNumber", SEMINAR_ATTENDANCE_ATTEMPT_NOT_AVAILABLE); } } - private void validAttendanceCode(Seminar seminar, SeminarAttendanceCodeRequest request) { + private void validAttendanceCode(Seminar seminar, SeminarAttendanceCodeRequest request, String key) { String seminarAttendanceCode = seminar.getAttendanceCode(); String inputAttendanceCode = request.attendanceCode(); if (!seminarAttendanceCode.equals(inputAttendanceCode)) { - throw new BusinessException(inputAttendanceCode, "inputAttendanceCode", SEMINAR_ATTENDANCE_CODE_NOT_AVAILABLE); + String attemptNumber = redisUtil.getData(key, String.class).orElseThrow(); + throw new BusinessException(attemptNumber, "attemptNumber", SEMINAR_ATTENDANCE_CODE_NOT_AVAILABLE); } } - private void checkExistsAttendanceSeminar(Seminar seminar, Member member) { - if (attendanceRepository.existsBySeminarAndMember(seminar, member)) { - throw new BusinessException(seminar.getName(), "seminar", SEMINAR_ATTENDANCE_DUPLICATE); + private void checkDuplicateAttendance(SeminarAttendance seminarAttendance) { + if (seminarAttendance.getSeminarAttendanceStatus().getType() != BEFORE_ATTENDANCE) { + throw new BusinessException(seminarAttendance.getSeminarAttendanceStatus(), "attendanceStatus", + SEMINAR_ATTENDANCE_DUPLICATE); } } diff --git a/src/main/java/com/keeper/homepage/global/error/ErrorCode.java b/src/main/java/com/keeper/homepage/global/error/ErrorCode.java index a0d715d98..1bebad1ca 100644 --- a/src/main/java/com/keeper/homepage/global/error/ErrorCode.java +++ b/src/main/java/com/keeper/homepage/global/error/ErrorCode.java @@ -31,6 +31,7 @@ public enum ErrorCode { SEMINAR_ATTENDANCE_CODE_NOT_AVAILABLE("올바르지 않은 출석 코드입니다.", HttpStatus.BAD_REQUEST), SEMINAR_ATTENDANCE_ATTEMPT_NOT_AVAILABLE("출석코드 입력 가능 횟수를 초과하여 출석이 불가능합니다.", HttpStatus.BAD_REQUEST), SEMINAR_ATTENDANCE_DUPLICATE("이미 출석을 완료한 계정입니다.", HttpStatus.CONFLICT), + SEMINAR_ATTENDANCE_UNABLE("활동회원이 아닌 회원은 출석이 불가합니다.", HttpStatus.BAD_REQUEST), // POST POST_CATEGORY_NOT_FOUND("존재하지 않는 게시글 카테고리입니다.", HttpStatus.NOT_FOUND), POST_NOT_FOUND("존재하지 않는 게시글입니다.", HttpStatus.NOT_FOUND), From b01c2dec5583118612b1ac17b49f6bbb18328d0f Mon Sep 17 00:00:00 2001 From: shkisme Date: Sun, 23 Jul 2023 17:06:01 +0900 Subject: [PATCH 10/12] =?UTF-8?q?fix:=20=EC=84=B8=EB=AF=B8=EB=82=98=20?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?api=20=EC=88=98=EC=A0=95=20-=20pathVariable=EB=A1=9C=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20id=EB=A5=BC=20=EB=B0=9B=EC=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20-=20=EC=B6=94=ED=9B=84=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=EC=9A=A9=20api=EA=B0=80=20=EB=90=A0=20=EC=98=88?= =?UTF-8?q?=EC=A0=95=EC=9E=84=EC=9D=84=20TODO=EB=A1=9C=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seminar/api/SeminarAttendanceController.java | 8 ++++---- .../seminar/application/SeminarAttendanceService.java | 10 ++++------ .../domain/seminar/entity/SeminarAttendance.java | 4 ++++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceController.java b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceController.java index f70388321..9f7296a99 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceController.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceController.java @@ -32,12 +32,12 @@ public ResponseEntity attendanceSeminar( return ResponseEntity.status(HttpStatus.CREATED).body(response); } - @PatchMapping + @PatchMapping("/{memberId}") // TODO: 관리자 권한으로 접근 가능하게 설정 public ResponseEntity changeAttendanceStatus( - @PathVariable Long seminarId, - @LoginMember Member member, + @PathVariable long seminarId, + @PathVariable long memberId, @RequestBody @Valid SeminarAttendanceStatusRequest request) { - seminarAttendanceService.changeStatus(seminarId, member, request); + seminarAttendanceService.changeStatus(seminarId, memberId, request); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java index 787bfae9f..310864dd1 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceService.java @@ -77,14 +77,12 @@ private void checkDuplicateAttendance(SeminarAttendance seminarAttendance) { } @Transactional - public void changeStatus(Long seminarId, Member member, SeminarAttendanceStatusRequest request) { - Nickname nickname = member.getProfile().getNickname(); - - Seminar seminar = seminarRepository.findById(seminarId) - .orElseThrow(() -> new BusinessException(seminarId, "seminarId", SEMINAR_NOT_FOUND)); + public void changeStatus(long seminarId, long memberId, SeminarAttendanceStatusRequest request) { + Member member = memberFindService.findById(memberId); + Seminar seminar = validSeminarFindService.findById(seminarId); SeminarAttendance seminarAttendance = attendanceRepository.findBySeminarAndMember(seminar, member) - .orElseThrow(() -> new BusinessException(nickname, "nickname", MEMBER_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(member.getNickname(), "nickname", MEMBER_NOT_FOUND)); seminarAttendance.changeStatus(request.excuse(), request.statusType()); } diff --git a/src/main/java/com/keeper/homepage/domain/seminar/entity/SeminarAttendance.java b/src/main/java/com/keeper/homepage/domain/seminar/entity/SeminarAttendance.java index 6d0e1df6d..7399b50c2 100644 --- a/src/main/java/com/keeper/homepage/domain/seminar/entity/SeminarAttendance.java +++ b/src/main/java/com/keeper/homepage/domain/seminar/entity/SeminarAttendance.java @@ -76,6 +76,10 @@ public void changeStatus(String excuse, SeminarAttendanceStatusType type) { } } + public void changeStatus(SeminarAttendanceStatusType type) { + seminarAttendanceStatus = getSeminarAttendanceStatusBy(type); + } + @Builder private SeminarAttendance(Seminar seminar, Member member, SeminarAttendanceStatus seminarAttendanceStatus, LocalDateTime attendTime) { From aee27ff904a4d49bcf0d71c54ae5abc1b34c0538 Mon Sep 17 00:00:00 2001 From: shkisme Date: Sun, 23 Jul 2023 17:07:19 +0900 Subject: [PATCH 11/12] =?UTF-8?q?test:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20api?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20x)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/MemberTestHelper.java | 1 + .../seminar/api/SeminarApiTestHelper.java | 15 ++++--- .../api/SeminarAttendanceControllerTest.java | 44 ++++++++++++------- .../seminar/api/SeminarControllerTest.java | 24 +++++----- .../SeminarAttendanceServiceTest.java | 7 +-- 5 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/test/java/com/keeper/homepage/domain/member/MemberTestHelper.java b/src/test/java/com/keeper/homepage/domain/member/MemberTestHelper.java index 5a16a04ac..0ca6401e6 100644 --- a/src/test/java/com/keeper/homepage/domain/member/MemberTestHelper.java +++ b/src/test/java/com/keeper/homepage/domain/member/MemberTestHelper.java @@ -17,6 +17,7 @@ import com.keeper.homepage.domain.member.entity.job.MemberHasMemberJob; import com.keeper.homepage.domain.member.entity.job.MemberJob; import com.keeper.homepage.domain.member.entity.job.MemberJob.MemberJobType; +import com.keeper.homepage.domain.member.entity.type.MemberType; import com.keeper.homepage.domain.thumbnail.entity.Thumbnail; import com.keeper.homepage.global.config.security.JwtTokenProvider; import com.keeper.homepage.global.util.thumbnail.ThumbnailTestHelper; diff --git a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarApiTestHelper.java b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarApiTestHelper.java index 25c871f9b..d53a4e612 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarApiTestHelper.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarApiTestHelper.java @@ -75,28 +75,31 @@ ResultActions deleteSeminarUsingApi(String token, Long seminarId) throws Excepti ResultActions attendanceSeminarUsingApi(String token, Long seminarId, SeminarAttendanceCodeRequest request) throws Exception { - return mockMvc.perform(post("/seminars/{seminarId}/attendances", seminarId) + return mockMvc.perform(patch("/seminars/{seminarId}/attendances", seminarId) .cookie(new Cookie(ACCESS_TOKEN.getTokenName(), token)) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))); } ResultActions attendanceSeminarUsingApi(String token, Long seminarId, String strJson) throws Exception { - return mockMvc.perform(post("/seminars/{seminarId}/attendances", seminarId) + return mockMvc.perform(patch("/seminars/{seminarId}/attendances", seminarId) .cookie(new Cookie(ACCESS_TOKEN.getTokenName(), token)) .contentType(MediaType.APPLICATION_JSON) .content(strJson)); } - ResultActions changeAttendanceStatusUsingApi(String token, Long seminarId, SeminarAttendanceStatusRequest request) throws Exception { - return mockMvc.perform(patch("/seminars/{seminarId}/attendances", seminarId) + ResultActions changeAttendanceStatusUsingApi(String token, long seminarId, long memberId, + SeminarAttendanceStatusRequest request) + throws Exception { + return mockMvc.perform(patch("/seminars/{seminarId}/attendances/{memberId}", seminarId, memberId) .cookie(new Cookie(ACCESS_TOKEN.getTokenName(), token)) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))); } - ResultActions changeAttendanceStatusUsingApi(String token, Long seminarId, String strJson) throws Exception { - return mockMvc.perform(patch("/seminars/{seminarId}/attendances", seminarId) + ResultActions changeAttendanceStatusUsingApi(String token, long seminarId, long memberId, String strJson) + throws Exception { + return mockMvc.perform(patch("/seminars/{seminarId}/attendances/{memberId}", seminarId, memberId) .cookie(new Cookie(ACCESS_TOKEN.getTokenName(), token)) .contentType(MediaType.APPLICATION_JSON) .content(strJson)); diff --git a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java index bcf064881..170de95fc 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarAttendanceControllerTest.java @@ -14,12 +14,15 @@ import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.keeper.homepage.domain.seminar.dto.request.SeminarAttendanceCodeRequest; import com.keeper.homepage.domain.seminar.dto.request.SeminarAttendanceStatusRequest; import com.keeper.homepage.domain.seminar.dto.request.SeminarStartRequest; import com.keeper.homepage.domain.seminar.dto.response.SeminarAttendanceResponse; +import com.keeper.homepage.domain.seminar.entity.Seminar; import com.keeper.homepage.domain.seminar.entity.SeminarAttendanceStatus.SeminarAttendanceStatusType; import java.time.LocalDateTime; import org.junit.jupiter.api.BeforeEach; @@ -79,6 +82,10 @@ public void should_success_attendanceSeminar() throws Exception { requestCookies( cookieWithName(ACCESS_TOKEN.getTokenName()).description( "ACCESS TOKEN %s".formatted(securedValue))), + pathParameters( + parameterWithName("seminarId") + .description("세미나의 ID") + ), requestFields( field("attendanceCode", "세미나 출석 코드")), responseFields( @@ -122,16 +129,17 @@ public void should_fail_invalidInputValue(String attendanceCode) throws Exceptio Long seminarId = createSeminarAndGetId(adminToken); startSeminarUsingApi(adminToken, seminarId, seminarStartRequest).andExpect(status().isOk()); - attendanceSeminarUsingApi(adminToken, seminarId, strJson.formatted(attendanceCode)).andExpect(status().isBadRequest()); + attendanceSeminarUsingApi(adminToken, seminarId, strJson.formatted(attendanceCode)).andExpect( + status().isBadRequest()); } @Test @DisplayName("세미나 지각을 성공한다.") public void should_success_when_attendanceSeminarLateness() throws Exception { - Long seminarId = seminarTestHelper.builder() - .attendanceCloseTime(now.plusMinutes(-2)) - .latenessCloseTime(now.plusMinutes(3)) - .build().getId(); + long seminarId = seminarService.save().id(); + Seminar seminar = seminarRepository.findById(seminarId).orElseThrow(); + seminar.changeCloseTime(now.plusMinutes(-2), now.plusMinutes(3)); + String attendanceCode = seminarRepository.findById(seminarId).orElseThrow() .getAttendanceCode(); @@ -152,10 +160,10 @@ public void should_success_when_attendanceSeminarLateness() throws Exception { @Test @DisplayName("세미나 지각 마감 시간이 지나면 결석 처리한다.") public void should_success_when_attendanceSeminarAbsence() throws Exception { - Long seminarId = seminarTestHelper.builder() - .attendanceCloseTime(now.plusMinutes(-2)) - .latenessCloseTime(now.plusMinutes(-1)) - .build().getId(); + long seminarId = seminarService.save().id(); + Seminar seminar = seminarRepository.findById(seminarId).orElseThrow(); + seminar.changeCloseTime(now.plusMinutes(-2), now.plusMinutes(-1)); + String attendanceCode = seminarRepository.findById(seminarId).orElseThrow() .getAttendanceCode(); @@ -173,6 +181,7 @@ public void should_success_when_attendanceSeminarAbsence() throws Exception { assertThat(statusType).isEqualTo(ABSENCE); } } + @Nested @DisplayName("세미나 출석 상태 변경 테스트") class SeminarAttendanceStatusTest { @@ -196,14 +205,19 @@ public void should_success_when_changeSeminarAttendanceStatus() throws Exception .build(); startSeminarUsingApi(adminToken, seminarId, seminarStartRequest).andExpect(status().isOk()); - attendanceSeminarUsingApi(adminToken, seminarId, request).andExpect(status().isCreated()); - changeAttendanceStatusUsingApi(adminToken, seminarId, statusRequest) + changeAttendanceStatusUsingApi(adminToken, seminarId, adminId, statusRequest) .andExpect(status().isNoContent()) .andDo(document("change-attendance-seminar", requestCookies( cookieWithName(ACCESS_TOKEN.getTokenName()).description( "ACCESS TOKEN %s".formatted(securedValue))), + pathParameters( + parameterWithName("memberId") + .description("출석 상태를 변경하고자 하는 회원의 ID"), + parameterWithName("seminarId") + .description("세미나의 ID") + ), requestFields( field("excuse", "세미나 사유"), field("statusType", "출석 타입")) @@ -226,8 +240,8 @@ public void should_success_when_emptyExcuse(String excuse) throws Exception { .build(); startSeminarUsingApi(adminToken, seminarId, seminarStartRequest).andExpect(status().isOk()); - attendanceSeminarUsingApi(adminToken, seminarId, request).andExpect(status().isCreated()); - changeAttendanceStatusUsingApi(adminToken, seminarId, strJson.formatted(excuse)).andExpect(status().isNoContent()); + changeAttendanceStatusUsingApi(adminToken, seminarId, adminId, strJson.formatted(excuse)).andExpect( + status().isNoContent()); } @ParameterizedTest @@ -246,8 +260,8 @@ public void should_fail_when_invalidValue(String statusType) throws Exception { .build(); startSeminarUsingApi(adminToken, seminarId, seminarStartRequest).andExpect(status().isOk()); - attendanceSeminarUsingApi(adminToken, seminarId, request).andExpect(status().isCreated()); - changeAttendanceStatusUsingApi(adminToken, seminarId, strJson.formatted(statusType)).andExpect(status().isBadRequest()); + changeAttendanceStatusUsingApi(adminToken, seminarId, adminId, strJson.formatted(statusType)).andExpect( + status().isBadRequest()); } } } diff --git a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java index 71d517f67..efb46a934 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/api/SeminarControllerTest.java @@ -4,7 +4,10 @@ import static com.keeper.homepage.domain.member.entity.job.MemberJob.MemberJobType.ROLE_회원; import static com.keeper.homepage.domain.member.entity.job.MemberJob.MemberJobType.ROLE_회장; import static com.keeper.homepage.global.config.security.data.JwtType.ACCESS_TOKEN; -import static com.keeper.homepage.global.restdocs.RestDocsHelper.*; +import static com.keeper.homepage.global.restdocs.RestDocsHelper.dateFormat; +import static com.keeper.homepage.global.restdocs.RestDocsHelper.dateTimeFormat; +import static com.keeper.homepage.global.restdocs.RestDocsHelper.field; +import static com.keeper.homepage.global.restdocs.RestDocsHelper.getSecuredValue; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; @@ -16,7 +19,6 @@ import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -108,6 +110,10 @@ public void should_successStartSeminar_when_admin() throws Exception { requestCookies( cookieWithName(ACCESS_TOKEN.getTokenName()).description( "ACCESS TOKEN %s".formatted(securedValue))), + pathParameters( + parameterWithName("seminarId") + .description("세미나의 ID") + ), requestFields( field("attendanceCloseTime", "출석 마감 시간", dateTimeFormat()), field("latenessCloseTime", "지각 마감 시간", dateTimeFormat())), @@ -282,14 +288,12 @@ public void should_successSearchSeminarUsingId_when_admin() throws Exception { em.clear(); var responseSeminarDescriptors = new FieldDescriptor[]{ - field("id", "세미나 ID"), + field("seminarId", "세미나 ID"), field("openTime", "세미나 생성 시간"), field("attendanceCloseTime", "출석 마감 시간"), field("latenessCloseTime", "지각 마감 시간"), - field("attendanceCode", "출석 코드"), - field("name", "세미나명"), - field("registerTime", "DB 생성 시간"), - field("updateTime", "DB 업데이트 시간") + field("seminarName", "세미나명"), + field("statusType", "세미나 출석 상태") }; searchSeminarUsingApi(adminToken, seminarId).andExpect(status().isOk()) @@ -309,12 +313,6 @@ public void should_successSearchSeminarUsingId_when_admin() throws Exception { public void should_failSearchSeminarNotExistId_when_admin() throws Exception { searchSeminarUsingApi(adminToken, 0L).andExpect(status().isNotFound()); } - - @Test - @DisplayName("관리자 권한이 아니면 id 값으로 세미나 조회를 실패한다.") - public void should_failSearchSeminarUsingId_when_notAdmin() throws Exception { - searchSeminarUsingApi(userToken, 2L).andExpect(status().isForbidden()); - } } @Nested diff --git a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceServiceTest.java b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceServiceTest.java index 819b97cbf..058da4f27 100644 --- a/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceServiceTest.java +++ b/src/test/java/com/keeper/homepage/domain/seminar/application/SeminarAttendanceServiceTest.java @@ -21,19 +21,20 @@ class AttemptLimitTest { @Test @DisplayName("출석코드 5회 초과 입력 시 출석이 불가능해야 한다.") public void 출석코드_5회_초과_입력_시_출석이_불가능해야_한다() throws Exception { - Seminar seminar = seminarTestHelper.generate(); Member member = memberTestHelper.generate(); + long seminarId = seminarService.save().id(); + Seminar seminar = seminarRepository.findById(seminarId).orElseThrow(); String invalidAttendanceCode = "12345"; for (int i = 0; i < 5; i++) { - assertThatThrownBy(() -> seminarAttendanceService.save(seminar.getId(), member, + assertThatThrownBy(() -> seminarAttendanceService.attendance(seminar.getId(), member, new SeminarAttendanceCodeRequest(invalidAttendanceCode))) .isInstanceOf(BusinessException.class) .hasMessageContaining("올바르지 않은 출석 코드입니다."); } - assertThatThrownBy(() -> seminarAttendanceService.save(seminar.getId(), member, + assertThatThrownBy(() -> seminarAttendanceService.attendance(seminar.getId(), member, new SeminarAttendanceCodeRequest(seminar.getAttendanceCode()))) .isInstanceOf(BusinessException.class) .hasMessageContaining("출석코드 입력 가능 횟수를 초과하여 출석이 불가능합니다."); From 9e91f6decaae29fb4bb465736b35799408d03bb4 Mon Sep 17 00:00:00 2001 From: shkisme Date: Sun, 23 Jul 2023 17:07:49 +0900 Subject: [PATCH 12/12] =?UTF-8?q?docs:=20api=20=EB=AC=B8=EC=84=9C=EC=97=90?= =?UTF-8?q?=20=EB=88=84=EB=9D=BD=EB=90=9C=20Path=20Parameters=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/seminar/seminar-attendance.adoc | 10 ++++++++++ src/docs/asciidoc/seminar/seminar.adoc | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/docs/asciidoc/seminar/seminar-attendance.adoc b/src/docs/asciidoc/seminar/seminar-attendance.adoc index cf45452d2..53542f156 100644 --- a/src/docs/asciidoc/seminar/seminar-attendance.adoc +++ b/src/docs/asciidoc/seminar/seminar-attendance.adoc @@ -15,6 +15,8 @@ link:../keeper.html[API 목록으로 돌아가기] == *세미나 출석* +NOTE: 활동회원만 출석이 가능합니다. + === 요청 ==== Request @@ -25,6 +27,10 @@ include::{snippets}/attendance-seminar/http-request.adoc[] include::{snippets}/attendance-seminar/request-cookies.adoc[] +==== Path Parameters + +include::{snippets}/attendance-seminar/path-parameters.adoc[] + ==== Request Fields include::{snippets}/attendance-seminar/request-fields.adoc[] @@ -51,6 +57,10 @@ include::{snippets}/change-attendance-seminar/http-request.adoc[] include::{snippets}/change-attendance-seminar/request-cookies.adoc[] +==== Path Parameters + +include::{snippets}/change-attendance-seminar/path-parameters.adoc[] + ==== Request Fields include::{snippets}/change-attendance-seminar/request-fields.adoc[] diff --git a/src/docs/asciidoc/seminar/seminar.adoc b/src/docs/asciidoc/seminar/seminar.adoc index 28692b3f0..d0a6f9f41 100644 --- a/src/docs/asciidoc/seminar/seminar.adoc +++ b/src/docs/asciidoc/seminar/seminar.adoc @@ -47,6 +47,10 @@ include::{snippets}/start-seminar/http-request.adoc[] include::{snippets}/start-seminar/request-cookies.adoc[] +==== Path Parameters + +include::{snippets}/start-seminar/path-parameters.adoc[] + ==== Request Fields include::{snippets}/start-seminar/request-fields.adoc[]