From ba852bc9e23350aa35d518f693890e5ed7833d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 22:34:08 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=ED=95=99=EA=B8=B0=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=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 --- .../AdminTimetableSemesterCreateRequest.java | 25 ++++++++++++++ .../AdminTimetableSemesterRepository.java | 13 +++++++ .../AdminTimetableSemesterService.java | 34 +++++++++++++++++++ .../koin/domain/timetable/model/Semester.java | 10 ++++++ 4 files changed, 82 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/admin/semester/dto/AdminTimetableSemesterCreateRequest.java create mode 100644 src/main/java/in/koreatech/koin/admin/semester/repository/AdminTimetableSemesterRepository.java create mode 100644 src/main/java/in/koreatech/koin/admin/semester/service/AdminTimetableSemesterService.java diff --git a/src/main/java/in/koreatech/koin/admin/semester/dto/AdminTimetableSemesterCreateRequest.java b/src/main/java/in/koreatech/koin/admin/semester/dto/AdminTimetableSemesterCreateRequest.java new file mode 100644 index 0000000000..ffd1a98773 --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/semester/dto/AdminTimetableSemesterCreateRequest.java @@ -0,0 +1,25 @@ +package in.koreatech.koin.admin.semester.dto; + +import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import in.koreatech.koin.domain.timetableV3.model.Term; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +@JsonNaming(SnakeCaseStrategy.class) +public record AdminTimetableSemesterCreateRequest( + @Schema(description = "연도", example = "2026", requiredMode = REQUIRED) + @NotNull(message = "연도는 필수입니다.") + @Positive(message = "연도는 양수여야 합니다.") + Integer year, + + @Schema(description = "학기", example = "FIRST", requiredMode = REQUIRED) + @NotNull(message = "학기는 필수입니다.") + Term term +) { + +} diff --git a/src/main/java/in/koreatech/koin/admin/semester/repository/AdminTimetableSemesterRepository.java b/src/main/java/in/koreatech/koin/admin/semester/repository/AdminTimetableSemesterRepository.java new file mode 100644 index 0000000000..9aeee093c6 --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/semester/repository/AdminTimetableSemesterRepository.java @@ -0,0 +1,13 @@ +package in.koreatech.koin.admin.semester.repository; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.timetable.model.Semester; +import in.koreatech.koin.domain.timetableV3.model.Term; + +public interface AdminTimetableSemesterRepository extends Repository { + + boolean existsBySemesterOrYearAndTerm(String semester, Integer year, Term term); + + Semester save(Semester semester); +} diff --git a/src/main/java/in/koreatech/koin/admin/semester/service/AdminTimetableSemesterService.java b/src/main/java/in/koreatech/koin/admin/semester/service/AdminTimetableSemesterService.java new file mode 100644 index 0000000000..83f000bc4e --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/semester/service/AdminTimetableSemesterService.java @@ -0,0 +1,34 @@ +package in.koreatech.koin.admin.semester.service; + +import static in.koreatech.koin.global.code.ApiResponseCode.DUPLICATE_SEMESTER; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.admin.semester.dto.AdminTimetableSemesterCreateRequest; +import in.koreatech.koin.admin.semester.repository.AdminTimetableSemesterRepository; +import in.koreatech.koin.domain.timetable.model.Semester; +import in.koreatech.koin.global.exception.CustomException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AdminTimetableSemesterService { + + private final AdminTimetableSemesterRepository adminTimetableSemesterRepository; + + @Transactional + public void createSemester(AdminTimetableSemesterCreateRequest request) { + Semester semester = Semester.of(request.year(), request.term()); + validateDuplicateSemester(semester); + adminTimetableSemesterRepository.save(semester); + } + + private void validateDuplicateSemester(Semester semester) { + if (adminTimetableSemesterRepository.existsBySemesterOrYearAndTerm( + semester.getSemester(), semester.getYear(), semester.getTerm() + )) { + throw CustomException.of(DUPLICATE_SEMESTER); + } + } +} diff --git a/src/main/java/in/koreatech/koin/domain/timetable/model/Semester.java b/src/main/java/in/koreatech/koin/domain/timetable/model/Semester.java index ef9dc1eeea..ab1d4200bd 100644 --- a/src/main/java/in/koreatech/koin/domain/timetable/model/Semester.java +++ b/src/main/java/in/koreatech/koin/domain/timetable/model/Semester.java @@ -50,4 +50,14 @@ public Semester(String semester, Integer year, Term term) { this.year = year; this.term = term; } + + public static Semester of(Integer year, Term term) { + String semester = switch (term) { + case FIRST -> year + "1"; + case SECOND -> year + "2"; + case SUMMER -> year + "-여름"; + case WINTER -> year + "-겨울"; + }; + return new Semester(semester, year, term); + } } From 9d3237691b8ba9026f37fee0b27bb08a871f8e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 22:35:08 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=ED=95=99=EA=B8=B0=20=EC=83=9D=EC=84=B1=20=EC=96=B4=EB=93=9C?= =?UTF-8?q?=EB=AF=BC=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/admin/history/enums/DomainType.java | 1 + .../semester/controller/AdminSemesterApi.java | 24 +++++++++++++++++++ .../controller/AdminSemesterController.java | 14 +++++++++++ 3 files changed, 39 insertions(+) diff --git a/src/main/java/in/koreatech/koin/admin/history/enums/DomainType.java b/src/main/java/in/koreatech/koin/admin/history/enums/DomainType.java index f6d11c71a8..f123591306 100644 --- a/src/main/java/in/koreatech/koin/admin/history/enums/DomainType.java +++ b/src/main/java/in/koreatech/koin/admin/history/enums/DomainType.java @@ -27,6 +27,7 @@ public enum DomainType { LANDS("복덕방"), LECTURES("강의"), + SEMESTER("학기"), COOP_SHOPS("생협 매장"), USERS("회원"), diff --git a/src/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterApi.java b/src/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterApi.java index 0b30837473..4dde4a9d42 100644 --- a/src/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterApi.java +++ b/src/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterApi.java @@ -1,6 +1,7 @@ package in.koreatech.koin.admin.semester.controller; import static in.koreatech.koin.admin.history.enums.DomainType.COOP_SEMESTER; +import static in.koreatech.koin.admin.history.enums.DomainType.SEMESTER; import static in.koreatech.koin.domain.user.model.UserType.ADMIN; import static in.koreatech.koin.global.code.ApiResponseCode.*; @@ -14,15 +15,38 @@ import in.koreatech.koin.admin.history.aop.AdminActivityLogging; import in.koreatech.koin.admin.semester.dto.AdminSemesterCreateRequest; import in.koreatech.koin.admin.semester.dto.AdminSemesterResponse; +import in.koreatech.koin.admin.semester.dto.AdminTimetableSemesterCreateRequest; import in.koreatech.koin.global.auth.Auth; import in.koreatech.koin.global.code.ApiResponseCodes; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @Tag(name = "(ADMIN) Semester : 학기", description = "관리자 권한으로 학기 정보를 관리한다") public interface AdminSemesterApi { + @ApiResponseCodes({ + OK, + INVALID_REQUEST_BODY, + UNAUTHORIZED_USER, + FORBIDDEN_ADMIN, + NOT_READABLE_HTTP_MESSAGE, + DUPLICATE_SEMESTER + }) + @Operation(summary = "(ADMIN) 시간표 학기 생성", description = """ + - `year`와 `term`을 사용해 시간표 학기를 생성합니다. + - `term`은 `FIRST`, `SECOND`, `SUMMER`, `WINTER` 중 하나입니다. + - 이미 같은 연도와 학기가 존재하면 생성하지 않습니다. + """) + @SecurityRequirement(name = "Jwt Authentication") + @AdminActivityLogging(domain = SEMESTER) + @PostMapping("/admin/semesters") + ResponseEntity createSemester( + @Valid @RequestBody AdminTimetableSemesterCreateRequest request, + @Auth(permit = {ADMIN}) Integer adminId + ); + @ApiResponseCodes({ OK, INVALID_SEMESTER_FORMAT, diff --git a/src/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterController.java b/src/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterController.java index 4f23450aea..df6195bb38 100644 --- a/src/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterController.java +++ b/src/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterController.java @@ -1,6 +1,7 @@ package in.koreatech.koin.admin.semester.controller; import static in.koreatech.koin.admin.history.enums.DomainType.COOP_SEMESTER; +import static in.koreatech.koin.admin.history.enums.DomainType.SEMESTER; import static in.koreatech.koin.domain.user.model.UserType.ADMIN; import java.util.List; @@ -14,7 +15,9 @@ import in.koreatech.koin.admin.history.aop.AdminActivityLogging; import in.koreatech.koin.admin.semester.dto.AdminSemesterCreateRequest; import in.koreatech.koin.admin.semester.dto.AdminSemesterResponse; +import in.koreatech.koin.admin.semester.dto.AdminTimetableSemesterCreateRequest; import in.koreatech.koin.admin.semester.service.AdminCoopShopSemesterService; +import in.koreatech.koin.admin.semester.service.AdminTimetableSemesterService; import in.koreatech.koin.global.auth.Auth; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -24,6 +27,17 @@ public class AdminSemesterController implements AdminSemesterApi { private final AdminCoopShopSemesterService adminCoopShopSemesterService; + private final AdminTimetableSemesterService adminTimetableSemesterService; + + @AdminActivityLogging(domain = SEMESTER) + @PostMapping("/admin/semesters") + public ResponseEntity createSemester( + @Valid @RequestBody AdminTimetableSemesterCreateRequest request, + @Auth(permit = {ADMIN}) Integer adminId + ) { + adminTimetableSemesterService.createSemester(request); + return ResponseEntity.ok().build(); + } @AdminActivityLogging(domain = COOP_SEMESTER) @PostMapping("/admin/coopshop/semesters")