diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4126ea6c..e29ebea7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -56,13 +56,15 @@ adapter/in → adapter/out (수평 의존) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Challenger extends BaseEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long userId; // ID 참조만 @Builder - private Challenger(...) { } + private Challenger(...) { + } // 도메인 로직 public void graduate() { @@ -102,9 +104,9 @@ public interface RegisterChallengerUseCase { } public record RegisterChallengerCommand( - Long userId, - Long gisuId, - ChallengerPart part + Long userId, + Long gisuId, + ChallengerPart part ) { // Validation in constructor if needed public RegisterChallengerCommand { @@ -115,6 +117,7 @@ public record RegisterChallengerCommand( // ❌ BAD public interface ChallengerUseCase { // 너무 포괄적인 이름 void register(Long userId, Long gisuId, String part); // primitive 타입 나열 + Challenger getById(Long id); // Entity 직접 반환 } ``` @@ -133,6 +136,7 @@ public interface ChallengerUseCase { // 너무 포괄적인 이름 // ✅ GOOD public interface LoadChallengerPort { Optional findById(Long id); + boolean existsByUserIdAndGisuId(Long userId, Long gisuId); } @@ -143,7 +147,9 @@ public interface SaveChallengerPort { // ❌ BAD public interface ChallengerPort { // Load/Save 분리 안됨 Challenger findById(Long id); // Optional 미사용 + void save(Challenger challenger); // 반환값 없음 + List findAllWithUserInfo(); // Response DTO 반환 } ``` @@ -169,7 +175,7 @@ public class ChallengerQueryService implements GetChallengerUseCase { @Override public ChallengerInfo getById(Long challengerId) { Challenger challenger = loadChallengerPort.findById(challengerId) - .orElseThrow(() -> new BusinessException(ErrorCode.CHALLENGER_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(ErrorCode.CHALLENGER_NOT_FOUND)); return ChallengerInfo.from(challenger); } } @@ -267,9 +273,9 @@ public class ChallengerController { ```java // ✅ GOOD - Request public record RegisterChallengerRequest( - @NotNull Long gisuId, - @NotNull ChallengerPart part -) { + @NotNull Long gisuId, + @NotNull ChallengerPart part + ) { public RegisterChallengerCommand toCommand(Long userId) { return new RegisterChallengerCommand(userId, gisuId, part); } @@ -277,17 +283,17 @@ public record RegisterChallengerRequest( // ✅ GOOD - Response public record ChallengerResponse( - Long id, - String userName, - String part, - String status + Long id, + String userName, + String part, + String status ) { public static ChallengerResponse from(ChallengerInfo info) { return new ChallengerResponse( - info.id(), - info.userName(), - info.part().name(), - info.status().name() + info.id(), + info.userName(), + info.part().name(), + info.status().name() ); } } @@ -330,12 +336,18 @@ public class ChallengerRequest { ```java // UseCase methods -register(), create(), update(), delete() // Command -getById(), getAll(), search(), find() // Query +register(),create(), + +update(),delete() // Command + +getById(),getAll(), + +search(),find() // Query // Port methods -save(), delete() // Save Port -findById(), findAll(), existsBy...() // Load Port +save(),delete() // Save Port + +findById(),findAll(),existsBy...() // Load Port // Controller endpoints POST /api/v1/{domains} // 생성 @@ -355,12 +367,23 @@ DELETE /api/v1/{domains}/{id} // 삭제 // ❌ 하나의 서비스가 너무 많은 책임 @Service public class ChallengerService { - public void register() { } - public void assignRole() { } - public void addRewardPenalty() { } - public void graduate() { } - public List search() { } - public void sendNotification() { } // 다른 도메인 책임 + public void register() { + } + + public void assignRole() { + } + + public void addRewardPenalty() { + } + + public void graduate() { + } + + public List search() { + } + + public void sendNotification() { + } // 다른 도메인 책임 } ``` @@ -377,7 +400,9 @@ public class Challenger { } // Service에서 직접 상태 변경 -challenger.setStatus(ChallengerStatus.GRADUATED); +challenger. + +setStatus(ChallengerStatus.GRADUATED); ``` **권장:** Entity에 도메인 로직 포함 @@ -453,7 +478,7 @@ public ApiResponse register( // ❌ Request Body에서 userId 받지 않음 public record RegisterRequest( - Long userId, // 보안 취약점 + Long userId, // 보안 취약점 ... ) ``` @@ -481,8 +506,9 @@ public void updateNotice(Long noticeId, UpdateCommand command, Long requesterId) ```java // ❌ N+1 발생 가능 List challengers = repository.findAll(); -for (Challenger c : challengers) { - User member = userRepository.findById(c.getUserId()); // N번 쿼리 +for( +Challenger c :challengers){ +User member = userRepository.findById(c.getUserId()); // N번 쿼리 } // ✅ Fetch Join 또는 별도 쿼리 @@ -513,25 +539,31 @@ List findByGisuId(Long gisuId); ```java // ✅ 한글 메서드명으로 명확하게 @Test -void 챌린저_등록_성공() { } +void 챌린저_등록_성공() { +} @Test -void 존재하지_않는_사용자면_USER_NOT_FOUND_예외() { } +void 존재하지_않는_사용자면_USER_NOT_FOUND_예외() { +} @Test -void 이미_등록된_챌린저면_중복_예외() { } +void 이미_등록된_챌린저면_중복_예외() { +} // ❌ 불명확한 테스트명 @Test -void test1() { } +void test1() { +} @Test -void registerTest() { } +void registerTest() { +} ``` ### Test Structure ```java + @Test void 챌린저_등록_성공() { // given - 테스트 데이터 준비 diff --git a/CLAUDE.md b/CLAUDE.md index 0b574a65..75a79cda 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -170,6 +170,15 @@ form ◄─────────────── member (독립적) | `Get` | 조회 | `GetChallengerUseCase` | | `Search` | 검색 | `SearchPostUseCase` | +### Manage 통합 옵션 + +CUD(Create, Update, Delete)를 하나의 인터페이스로 통합하고 싶다면 `Manage` 접두사를 사용할 수 있습니다. + +| 대상 | 개별형 | 통합형 | +|-------------|---------------------------------------------------------------------|-----------------------| +| **UseCase** | `CreateSchoolUseCase`, `UpdateSchoolUseCase`, `DeleteSchoolUseCase` | `ManageSchoolUseCase` | +| **Port** | `SaveSchoolPort` | `ManageSchoolPort` | + --- ## Code Examples @@ -511,11 +520,11 @@ public class AttendanceEventListener { ```json { - "success": true, - "data": { - ... - }, - "error": null + "success": true, + "data": { + ... + }, + "error": null } ``` @@ -523,12 +532,12 @@ public class AttendanceEventListener { ```json { - "success": false, - "data": null, - "error": { - "code": "CHALLENGER_NOT_FOUND", - "message": "챌린저를 찾을 수 없습니다." - } + "success": false, + "data": null, + "error": { + "code": "CHALLENGER_NOT_FOUND", + "message": "챌린저를 찾을 수 없습니다." + } } ``` @@ -536,18 +545,18 @@ public class AttendanceEventListener { ```json { - "success": true, - "data": { - "content": [ - ... - ], - "page": 0, - "size": 20, - "totalElements": 100, - "totalPages": 5, - "hasNext": true - }, - "error": null + "success": true, + "data": { + "content": [ + ... + ], + "page": 0, + "size": 20, + "totalElements": 100, + "totalPages": 5, + "hasNext": true + }, + "error": null } ``` diff --git a/src/docs/asciidoc/api/chapter/chapter.adoc b/src/docs/asciidoc/api/chapter/chapter.adoc new file mode 100644 index 00000000..13e4d671 --- /dev/null +++ b/src/docs/asciidoc/api/chapter/chapter.adoc @@ -0,0 +1,10 @@ +[[chapter-get-list]] +=== 지부 목록 조회 + +operation::chapter-query-controller-test/지부_목록을_조회합니다[snippets='http-request,http-response'] + +[[chapter-create]] +=== 신규 지부 생성 + +operation::chapter-controller-test/신규_지부를_생성한다[snippets='http-request,request-fields,http-response'] + diff --git a/src/docs/asciidoc/api/gisu/gisu.adoc b/src/docs/asciidoc/api/gisu/gisu.adoc new file mode 100644 index 00000000..cffffe73 --- /dev/null +++ b/src/docs/asciidoc/api/gisu/gisu.adoc @@ -0,0 +1,25 @@ +[[gisu-list]] +=== 기수 목록 조회 + +operation::gisu-query-controller-test/기수_목록을_조회한다[snippets='http-request,http-response,response-fields'] + +[[gisu-create]] +=== 신규 기수 추가 + +operation::gisu-controller-test/신규_기수를_추가한다[snippets='http-request,request-fields,http-response'] + +[[gisu-update]] +=== 기수 정보 수정 + +operation::gisu-controller-test/기수_정보를_수정한다[snippets='http-request,path-parameters,request-fields,http-response'] + +[[gisu-delete]] +=== 기수 삭제 + +operation::gisu-controller-test/기수를_삭제한다[snippets='http-request,path-parameters,http-response'] + +[[gisu-set-current]] +=== 현재 기수 설정 + +operation::gisu-controller-test/현재_기수를_설정한다[snippets='http-request,path-parameters,http-response'] + diff --git a/src/docs/asciidoc/api/school/school.adoc b/src/docs/asciidoc/api/school/school.adoc new file mode 100644 index 00000000..716aeb36 --- /dev/null +++ b/src/docs/asciidoc/api/school/school.adoc @@ -0,0 +1,30 @@ +[[school-list]] +=== 총괄 학교 목록 조회 + +operation::school-query-controller-test/학교_목록을_조회합니다[snippets='http-request,query-parameters,http-response'] + +[[school-detail]] +=== 총괄 학교 상세 조회 + +operation::school-query-controller-test/학교_상세정보를_조회합니다[snippets='http-request,path-parameters,http-response'] + +[[school-create]] +=== 총괄 학교 등록 + +operation::school-controller-test/총괄_신규학교를_추가한다[snippets='http-request,request-fields,http-response'] + +[[school-delete]] +=== 총괄 학교 삭제 + +operation::school-controller-test/총괄_학교를_제거한다[snippets='http-request,path-parameters,http-response'] + +[[school-delete-bulk]] +=== 총괄 학교 일괄 삭제 + +operation::school-controller-test/총괄_학교를_일괄_삭제한다[snippets='http-request,request-fields,http-response'] + +[[school-update]] +=== 총괄 학교정보 수정 + +operation::school-controller-test/총괄_학교정보를_수정한다[snippets='http-request,path-parameters,request-fields,http-response'] + diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 6a3f207c..db7f5170 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -9,3 +9,17 @@ endif::[] :toclevels: 2 :sectlinks: +[[School-API]] +== 학교 API + +include::api/school/school.adoc[] + +[[Gisu-API]] +== 기수 API + +include::api/gisu/gisu.adoc[] + +[[Chapter-API]] +== 지부 API + +include::api/chapter/chapter.adoc[] diff --git a/src/main/java/com/umc/product/UmcProductApplication.java b/src/main/java/com/umc/product/UmcProductApplication.java index b91ca3ea..4bedacfb 100644 --- a/src/main/java/com/umc/product/UmcProductApplication.java +++ b/src/main/java/com/umc/product/UmcProductApplication.java @@ -2,10 +2,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication -@EnableJpaAuditing public class UmcProductApplication { public static void main(String[] args) { diff --git a/src/main/java/com/umc/product/common/dto/request/PageRequest.java b/src/main/java/com/umc/product/common/dto/request/PageRequest.java new file mode 100644 index 00000000..aba023ec --- /dev/null +++ b/src/main/java/com/umc/product/common/dto/request/PageRequest.java @@ -0,0 +1,13 @@ +package com.umc.product.common.dto.request; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; + +public record PageRequest( + // common으로 빼도 괜찮을 거 같습니다 + @Min(1) int page, + @Min(1) @Max(100) int limit) { + public long offset() { + return (long) (page - 1) * limit; + } +} \ No newline at end of file diff --git a/src/main/java/com/umc/product/common/dto/request/PageResult.java b/src/main/java/com/umc/product/common/dto/request/PageResult.java new file mode 100644 index 00000000..2d464931 --- /dev/null +++ b/src/main/java/com/umc/product/common/dto/request/PageResult.java @@ -0,0 +1,13 @@ +package com.umc.product.common.dto.request; + +import java.util.List; + +public record PageResult( + List items, + long totalCount, + int page, + int limit) { + public long totalPages() { + return (totalCount + limit - 1) / limit; + } +} \ No newline at end of file diff --git a/src/main/java/com/umc/product/global/config/JpaConfig.java b/src/main/java/com/umc/product/global/config/JpaConfig.java new file mode 100644 index 00000000..fa5d6d7c --- /dev/null +++ b/src/main/java/com/umc/product/global/config/JpaConfig.java @@ -0,0 +1,9 @@ +package com.umc.product.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaConfig { +} diff --git a/src/main/java/com/umc/product/global/security/CustomAuthorizationManager.java b/src/main/java/com/umc/product/global/security/CustomAuthorizationManager.java new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/com/umc/product/global/security/UserPrincipal.java b/src/main/java/com/umc/product/global/security/UserPrincipal.java new file mode 100644 index 00000000..cbd2d67c --- /dev/null +++ b/src/main/java/com/umc/product/global/security/UserPrincipal.java @@ -0,0 +1,12 @@ +package com.umc.product.global.security; + +import java.util.List; +import lombok.Builder; + +@Builder +public record UserPrincipal( + Long userId, + List roles +) { + +} \ No newline at end of file diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/ChapterController.java b/src/main/java/com/umc/product/organization/adapter/in/web/ChapterController.java new file mode 100644 index 00000000..18f4e7ca --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/ChapterController.java @@ -0,0 +1,23 @@ +package com.umc.product.organization.adapter.in.web; + +import com.umc.product.organization.adapter.in.web.dto.request.CreateChapterRequest; +import com.umc.product.organization.application.port.in.command.ManageChapterUseCase; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +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; + +@RestController +@RequestMapping("/api/v1/admin/chapters") +@RequiredArgsConstructor +public class ChapterController { + + private final ManageChapterUseCase manageChapterUseCase; + + @PostMapping + public Long createChapter(@RequestBody @Valid CreateChapterRequest request) { + return manageChapterUseCase.create(request.toCommand()); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java b/src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java new file mode 100644 index 00000000..ddbc10b6 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java @@ -0,0 +1,24 @@ +package com.umc.product.organization.adapter.in.web; + +import com.umc.product.organization.adapter.in.web.dto.response.ChapterListResponse; +import com.umc.product.organization.application.port.in.query.GetChapterUseCase; +import com.umc.product.organization.application.port.in.query.dto.ChapterInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/admin/chapters") +@RequiredArgsConstructor +public class ChapterQueryController { + + private final GetChapterUseCase getChapterUseCase; + + @GetMapping + public ChapterListResponse getAllChapter() { + List chapters = getChapterUseCase.getAllChapter(); + return ChapterListResponse.from(chapters); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java b/src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java new file mode 100644 index 00000000..5ce0ec88 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java @@ -0,0 +1,42 @@ +package com.umc.product.organization.adapter.in.web; + +import com.umc.product.organization.adapter.in.web.dto.request.CreateGisuRequest; +import com.umc.product.organization.adapter.in.web.dto.request.UpdateGisuRequest; +import com.umc.product.organization.application.port.in.command.ManageGisuUseCase; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +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; + +@RestController +@RequestMapping("/api/v1/admin/gisu") +@RequiredArgsConstructor +public class GisuController { + + private final ManageGisuUseCase manageGisuUseCase; + + @PostMapping + public Long createGisu(@RequestBody @Valid CreateGisuRequest request) { + return manageGisuUseCase.register(request.toCommand()); + } + + @PatchMapping("/{gisuId}") + public void updateGisu(@PathVariable Long gisuId, @RequestBody @Valid UpdateGisuRequest request) { + manageGisuUseCase.updateGisu(request.toCommand(gisuId)); + } + + @DeleteMapping("/{gisuId}") + public void deleteGisu(@PathVariable Long gisuId) { + manageGisuUseCase.deleteGisu(gisuId); + } + + @PostMapping("/{gisuId}/current") + public void setCurrentGisu(@PathVariable Long gisuId) { + manageGisuUseCase.setCurrentGisu(gisuId); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java b/src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java new file mode 100644 index 00000000..32bc4ba6 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java @@ -0,0 +1,21 @@ +package com.umc.product.organization.adapter.in.web; + +import com.umc.product.organization.adapter.in.web.dto.response.GisuListResponse; +import com.umc.product.organization.application.port.in.query.GetGisuUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/admin/gisu") +@RequiredArgsConstructor +public class GisuQueryController { + + private final GetGisuUseCase getGisuUseCase; + + @GetMapping + public GisuListResponse getGisuList() { + return GisuListResponse.from(getGisuUseCase.getList()); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java b/src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java new file mode 100644 index 00000000..9bbff075 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java @@ -0,0 +1,44 @@ +package com.umc.product.organization.adapter.in.web; + +import com.umc.product.organization.adapter.in.web.dto.request.CreateSchoolRequest; +import com.umc.product.organization.adapter.in.web.dto.request.DeleteSchoolsRequest; +import com.umc.product.organization.adapter.in.web.dto.request.UpdateSchoolRequest; +import com.umc.product.organization.application.port.in.command.ManageSchoolUseCase; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +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; + +@RestController +@RequestMapping("/api/v1/admin/schools") +@RequiredArgsConstructor +public class SchoolController { + + private final ManageSchoolUseCase manageSchoolUseCase; + + @PostMapping + public void createSchool(@RequestBody @Valid CreateSchoolRequest createSchoolRequest) { + manageSchoolUseCase.register(createSchoolRequest.toCommand()); + } + + @PatchMapping("/{schoolId}") + public void updateSchool(@PathVariable Long schoolId, @RequestBody @Valid UpdateSchoolRequest updateSchoolRequest) { + manageSchoolUseCase.updateSchool(updateSchoolRequest.toCommand()); + } + + @DeleteMapping("/{schoolId}") + public void deleteSchool(@PathVariable Long schoolId) { + manageSchoolUseCase.deleteSchool(schoolId); + } + + @DeleteMapping + public void deleteSchools(@RequestBody @Valid DeleteSchoolsRequest request) { + manageSchoolUseCase.deleteSchools(request.schoolIds()); + } +} + diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java b/src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java new file mode 100644 index 00000000..73598689 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java @@ -0,0 +1,37 @@ +package com.umc.product.organization.adapter.in.web; + +import com.umc.product.global.response.PageResponse; +import com.umc.product.organization.adapter.in.web.dto.request.SchoolListRequest; +import com.umc.product.organization.adapter.in.web.dto.response.SchoolDetailResponse; +import com.umc.product.organization.adapter.in.web.dto.response.SchoolListItemResponse; +import com.umc.product.organization.application.port.in.query.GetSchoolUseCase; +import com.umc.product.organization.application.port.in.query.dto.SchoolInfo; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/admin/schools") +@RequiredArgsConstructor +public class SchoolQueryController { + + private final GetSchoolUseCase getSchoolUseCase; + + @GetMapping + public PageResponse getSchoolList(@ModelAttribute SchoolListRequest request, + Pageable pageable) { + return PageResponse.of(getSchoolUseCase.getList(request.toCondition(), pageable), SchoolListItemResponse::of); + } + + @GetMapping("/{schoolId}") + public SchoolDetailResponse getSchoolDetail(@PathVariable Long schoolId) { + + SchoolInfo schoolInfo = getSchoolUseCase.getSchoolDetail(schoolId); + + return SchoolDetailResponse.of(schoolInfo); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateChapterRequest.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateChapterRequest.java new file mode 100644 index 00000000..11f750c7 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateChapterRequest.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.adapter.in.web.dto.request; + +import com.umc.product.organization.application.port.in.command.dto.CreateChapterCommand; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record CreateChapterRequest(@NotNull Long gisuId, @NotBlank String name) { + public CreateChapterCommand toCommand() { + return new CreateChapterCommand(gisuId, name); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java new file mode 100644 index 00000000..7fa0db1f --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.adapter.in.web.dto.request; + +import com.umc.product.organization.application.port.in.command.dto.CreateGisuCommand; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; + +public record CreateGisuRequest(@NotNull Long number, @NotNull LocalDate startAt, @NotNull LocalDate endAt) { + public CreateGisuCommand toCommand() { + return new CreateGisuCommand(number, startAt, endAt); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java new file mode 100644 index 00000000..1b7a955b --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java @@ -0,0 +1,17 @@ +package com.umc.product.organization.adapter.in.web.dto.request; + +import com.umc.product.organization.application.port.in.command.dto.CreateSchoolCommand; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder +public record CreateSchoolRequest( + @NotBlank String schoolName, + @NotBlank String chapterId, + @NotNull String remark +) { + public CreateSchoolCommand toCommand() { + return new CreateSchoolCommand(schoolName, chapterId, remark); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java new file mode 100644 index 00000000..cac2dd7e --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java @@ -0,0 +1,9 @@ +package com.umc.product.organization.adapter.in.web.dto.request; + +import jakarta.validation.constraints.NotEmpty; +import java.util.List; + +public record DeleteSchoolsRequest( + @NotEmpty List schoolIds +) { +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java new file mode 100644 index 00000000..7e9e5a76 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java @@ -0,0 +1,17 @@ +package com.umc.product.organization.adapter.in.web.dto.request; + +import com.umc.product.organization.application.port.in.query.dto.SchoolSearchCondition; + +public record SchoolListRequest( + String keyword, + Long chapterId +) { + + public SchoolSearchCondition toCondition() { + return new SchoolSearchCondition( + keyword, + chapterId + ); + } +} + diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java new file mode 100644 index 00000000..600dd7f5 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.adapter.in.web.dto.request; + +import com.umc.product.organization.application.port.in.command.dto.UpdateGisuCommand; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; + +public record UpdateGisuRequest(@NotNull LocalDate startAt, @NotNull LocalDate endAt) { + public UpdateGisuCommand toCommand(Long gisuId) { + return new UpdateGisuCommand(gisuId, startAt, endAt); + } +} \ No newline at end of file diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java new file mode 100644 index 00000000..80ec1fd3 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java @@ -0,0 +1,13 @@ +package com.umc.product.organization.adapter.in.web.dto.request; + +import com.umc.product.organization.application.port.in.command.dto.UpdateSchoolCommand; + +public record UpdateSchoolRequest( + String schoolName, + String chapterId, + String remark +) { + public UpdateSchoolCommand toCommand() { + return new UpdateSchoolCommand(schoolName, chapterId, remark); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java new file mode 100644 index 00000000..1ec97522 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java @@ -0,0 +1,27 @@ +package com.umc.product.organization.adapter.in.web.dto.response; + +import com.umc.product.organization.application.port.in.query.dto.ChapterInfo; +import java.util.List; + +public record ChapterListResponse( + List chapters +) { + public static ChapterListResponse from(List infos) { + List items = infos.stream() + .map(ChapterItem::from) + .toList(); + return new ChapterListResponse(items); + } + + public record ChapterItem( + Long id, + String name + ) { + public static ChapterItem from(ChapterInfo info) { + return new ChapterItem( + info.id(), + info.name() + ); + } + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java new file mode 100644 index 00000000..0a9065ff --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.adapter.in.web.dto.response; + +import com.umc.product.organization.application.port.in.query.dto.GisuInfo; +import java.util.List; + +public record GisuListResponse(List gisuList) { + public static GisuListResponse from(List gisuInfoList) { + List responses = gisuInfoList.stream().map(GisuResponse::from).toList(); + return new GisuListResponse(responses); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java new file mode 100644 index 00000000..21454568 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java @@ -0,0 +1,10 @@ +package com.umc.product.organization.adapter.in.web.dto.response; + +import com.umc.product.organization.application.port.in.query.dto.GisuInfo; +import java.time.LocalDate; + +public record GisuResponse(Long gisuId, Long number, LocalDate startAt, LocalDate endAt, boolean isActive) { + public static GisuResponse from(GisuInfo info) { + return new GisuResponse(info.gisuId(), info.number(), info.startAt(), info.endAt(), info.isActive()); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java new file mode 100644 index 00000000..f345af03 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java @@ -0,0 +1,12 @@ +package com.umc.product.organization.adapter.in.web.dto.response; + +import com.umc.product.organization.application.port.in.query.dto.SchoolInfo; +import java.time.LocalDate; + +public record SchoolDetailResponse(Long chapterId, String chapterName, String schoolName, Long schoolId, String remark, + LocalDate createdAt, LocalDate updatedAt) { + public static SchoolDetailResponse of(SchoolInfo info) { + return new SchoolDetailResponse(info.chapterId(), info.chapterName(), info.schoolName(), info.schoolId(), + info.remark(), info.createdAt(), info.updatedAt()); + } +} diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java new file mode 100644 index 00000000..e59f96b5 --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java @@ -0,0 +1,14 @@ +package com.umc.product.organization.adapter.in.web.dto.response; + +import com.umc.product.organization.application.port.in.query.dto.SchoolListItemInfo; +import java.time.LocalDate; + +public record SchoolListItemResponse(Long schoolId, String schoolName, Long chapterId, String chapterName, + LocalDate createdAt, boolean isActive) { + + public static SchoolListItemResponse of(SchoolListItemInfo summary) { + return new SchoolListItemResponse(summary.schoolId(), summary.schoolName(), summary.chapterId(), + summary.chapterName(), summary.createdAt(), summary.isActive()); + } +} + diff --git a/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java new file mode 100644 index 00000000..3318667b --- /dev/null +++ b/src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java @@ -0,0 +1,15 @@ +package com.umc.product.organization.adapter.in.web.dto.response; + +import com.umc.product.organization.application.port.in.query.dto.SchoolSummary; + +public record SchoolSummaryResponse( + Long schoolId, + String schoolName +) { + public static SchoolSummaryResponse from(SchoolSummary summary) { + return new SchoolSummaryResponse( + summary.schoolId(), + summary.schoolName() + ); + } +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/command/ManageChapterUseCase.java b/src/main/java/com/umc/product/organization/application/port/in/command/ManageChapterUseCase.java new file mode 100644 index 00000000..4a60349c --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/command/ManageChapterUseCase.java @@ -0,0 +1,8 @@ +package com.umc.product.organization.application.port.in.command; + +import com.umc.product.organization.application.port.in.command.dto.CreateChapterCommand; + +public interface ManageChapterUseCase { + + Long create(CreateChapterCommand command); +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java b/src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java new file mode 100644 index 00000000..312b6066 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java @@ -0,0 +1,15 @@ +package com.umc.product.organization.application.port.in.command; + +import com.umc.product.organization.application.port.in.command.dto.CreateGisuCommand; +import com.umc.product.organization.application.port.in.command.dto.UpdateGisuCommand; + +public interface ManageGisuUseCase { + + Long register(CreateGisuCommand command); + + void updateGisu(UpdateGisuCommand command); + + void deleteGisu(Long gisuId); + + void setCurrentGisu(Long gisuId); +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java b/src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java new file mode 100644 index 00000000..041682c5 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java @@ -0,0 +1,16 @@ +package com.umc.product.organization.application.port.in.command; + +import com.umc.product.organization.application.port.in.command.dto.CreateSchoolCommand; +import com.umc.product.organization.application.port.in.command.dto.UpdateSchoolCommand; +import java.util.List; + +public interface ManageSchoolUseCase { + + void register(CreateSchoolCommand command); + + void updateSchool(UpdateSchoolCommand command); + + void deleteSchool(Long schoolId); + + void deleteSchools(List schoolIds); +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateChapterCommand.java b/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateChapterCommand.java new file mode 100644 index 00000000..ca1b3a5d --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateChapterCommand.java @@ -0,0 +1,4 @@ +package com.umc.product.organization.application.port.in.command.dto; + +public record CreateChapterCommand(Long gisuId, String name) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java b/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java new file mode 100644 index 00000000..e62eee07 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java @@ -0,0 +1,6 @@ +package com.umc.product.organization.application.port.in.command.dto; + +import java.time.LocalDate; + +public record CreateGisuCommand(Long number, LocalDate startAt, LocalDate endAt) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java b/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java new file mode 100644 index 00000000..0fddf141 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java @@ -0,0 +1,8 @@ +package com.umc.product.organization.application.port.in.command.dto; + +public record CreateSchoolCommand( + String schoolName, + String chapterId, + String remark +) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java b/src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java new file mode 100644 index 00000000..f3420627 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java @@ -0,0 +1,6 @@ +package com.umc.product.organization.application.port.in.command.dto; + +import java.time.LocalDate; + +public record UpdateGisuCommand(Long gisuId, LocalDate startAt, LocalDate endAt) { +} \ No newline at end of file diff --git a/src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java b/src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java new file mode 100644 index 00000000..e10415aa --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java @@ -0,0 +1,9 @@ +package com.umc.product.organization.application.port.in.command.dto; + +public record UpdateSchoolCommand( + String schoolName, + String chapterId, + String remark +) { + +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java b/src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java new file mode 100644 index 00000000..b6cea775 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java @@ -0,0 +1,9 @@ +package com.umc.product.organization.application.port.in.query; + +import com.umc.product.organization.application.port.in.query.dto.ChapterInfo; +import java.util.List; + +public interface GetChapterUseCase { + + List getAllChapter(); +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java b/src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java new file mode 100644 index 00000000..b21fad85 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.application.port.in.query; + +import com.umc.product.organization.application.port.in.query.dto.GisuInfo; +import java.util.List; + +public interface GetGisuUseCase { + + List getList(); + + GisuInfo getById(Long gisuId); +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java b/src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java new file mode 100644 index 00000000..917c5258 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java @@ -0,0 +1,15 @@ +package com.umc.product.organization.application.port.in.query; + +import com.umc.product.organization.application.port.in.query.dto.SchoolInfo; +import com.umc.product.organization.application.port.in.query.dto.SchoolListItemInfo; +import com.umc.product.organization.application.port.in.query.dto.SchoolSearchCondition; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + + +public interface GetSchoolUseCase { + + Page getList(SchoolSearchCondition condition, Pageable pageable); + + SchoolInfo getSchoolDetail(Long schoolId); +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/dto/ChapterInfo.java b/src/main/java/com/umc/product/organization/application/port/in/query/dto/ChapterInfo.java new file mode 100644 index 00000000..ab77d99d --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/dto/ChapterInfo.java @@ -0,0 +1,4 @@ +package com.umc.product.organization.application.port.in.query.dto; + +public record ChapterInfo(Long id, String name) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/dto/GisuInfo.java b/src/main/java/com/umc/product/organization/application/port/in/query/dto/GisuInfo.java new file mode 100644 index 00000000..829013c7 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/dto/GisuInfo.java @@ -0,0 +1,6 @@ +package com.umc.product.organization.application.port.in.query.dto; + +import java.time.LocalDate; + +public record GisuInfo(Long gisuId, Long number, LocalDate startAt, LocalDate endAt, boolean isActive) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolDeleteSearchCondition.java b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolDeleteSearchCondition.java new file mode 100644 index 00000000..b48b6089 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolDeleteSearchCondition.java @@ -0,0 +1,7 @@ +package com.umc.product.organization.application.port.in.query.dto; + +public record SchoolDeleteSearchCondition( + String keyword, + Long chapterId +) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java new file mode 100644 index 00000000..9f074494 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java @@ -0,0 +1,14 @@ +package com.umc.product.organization.application.port.in.query.dto; + +import java.time.LocalDate; + +public record SchoolInfo( + Long chapterId, + String chapterName, + String schoolName, + Long schoolId, + String remark, + LocalDate createdAt, + LocalDate updatedAt +) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolListItemInfo.java b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolListItemInfo.java new file mode 100644 index 00000000..dc5c0f99 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolListItemInfo.java @@ -0,0 +1,14 @@ +package com.umc.product.organization.application.port.in.query.dto; + +import java.time.LocalDate; + +public record SchoolListItemInfo( + Long schoolId, + String schoolName, + Long chapterId, + String chapterName, + LocalDate createdAt, + boolean isActive +) { + +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSearchCondition.java b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSearchCondition.java new file mode 100644 index 00000000..d82adea1 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSearchCondition.java @@ -0,0 +1,9 @@ +package com.umc.product.organization.application.port.in.query.dto; + +public record SchoolSearchCondition( + String keyword, + Long chapterId +) { + +} + diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSummary.java b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSummary.java new file mode 100644 index 00000000..5605de20 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSummary.java @@ -0,0 +1,7 @@ +package com.umc.product.organization.application.port.in.query.dto; + +public record SchoolSummary( + Long schoolId, + String schoolName +) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java b/src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java new file mode 100644 index 00000000..7a955b44 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java @@ -0,0 +1,13 @@ +package com.umc.product.organization.application.port.in.query.dto; + +import java.time.LocalDate; + +public record UpdateSchoolInfo( + String newSchoolName, + Long chapterId, + String chapterName, + String remark, + LocalDate createdAt, + LocalDate updatedAt +) { +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java b/src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java new file mode 100644 index 00000000..54ce8d38 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java @@ -0,0 +1,15 @@ +package com.umc.product.organization.application.port.out.command; + +import com.umc.product.organization.domain.CentralOrganization; +import java.util.Optional; + +public interface ManageCentralOrganizationPort { + + Optional findById(Long id); + + CentralOrganization save(CentralOrganization organization); + + void delete(CentralOrganization organization); + + +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java b/src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java new file mode 100644 index 00000000..d6f9c170 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java @@ -0,0 +1,12 @@ +package com.umc.product.organization.application.port.out.command; + + +import com.umc.product.organization.domain.Chapter; + +public interface ManageChapterPort { + + + Chapter save(Chapter chapter); + + void delete(Chapter chapter); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java b/src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java new file mode 100644 index 00000000..0729a546 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.application.port.out.command; + + +import com.umc.product.organization.domain.ChapterSchool; + +public interface ManageChapterSchoolPort { + + ChapterSchool save(ChapterSchool chapterSchool); + + void delete(ChapterSchool chapterSchool); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java b/src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java new file mode 100644 index 00000000..aa40e46a --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.application.port.out.command; + + +import com.umc.product.organization.domain.Gisu; + +public interface ManageGisuPort { + + Gisu save(Gisu gisu); + + void delete(Gisu gisu); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java b/src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java new file mode 100644 index 00000000..840cf784 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.application.port.out.command; + + +import com.umc.product.organization.domain.School; + +public interface ManageSchoolPort { + + School save(School school); + + void delete(School school); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java b/src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java new file mode 100644 index 00000000..c9182ad9 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.application.port.out.command; + + +import com.umc.product.organization.domain.StudyGroupMember; + +public interface ManageStudyGroupMemberPort { + + StudyGroupMember save(StudyGroupMember studyGroupMember); + + void delete(StudyGroupMember studyGroupMember); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java b/src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java new file mode 100644 index 00000000..3bc47f88 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.application.port.out.command; + + +import com.umc.product.organization.domain.StudyGroup; + +public interface ManageStudyGroupPort { + + StudyGroup save(StudyGroup studyGroup); + + void delete(StudyGroup studyGroup); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/query/LoadCentralOrganizationPort.java b/src/main/java/com/umc/product/organization/application/port/out/query/LoadCentralOrganizationPort.java new file mode 100644 index 00000000..8e7e23e8 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/query/LoadCentralOrganizationPort.java @@ -0,0 +1,10 @@ +package com.umc.product.organization.application.port.out.query; + +import com.umc.product.organization.domain.CentralOrganization; +import java.util.Optional; + +public interface LoadCentralOrganizationPort { + + Optional findById(Long id); + +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java b/src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java new file mode 100644 index 00000000..1ef222ed --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java @@ -0,0 +1,15 @@ +package com.umc.product.organization.application.port.out.query; + +import com.umc.product.organization.domain.Chapter; +import com.umc.product.organization.domain.Gisu; +import java.util.List; +import java.util.Optional; + +public interface LoadChapterPort { + + Optional findById(Long id); + + List findAll(); + + List findAllByGisu(Gisu gisu); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java b/src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java new file mode 100644 index 00000000..2ba08f61 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java @@ -0,0 +1,16 @@ +package com.umc.product.organization.application.port.out.query; + +import com.umc.product.organization.domain.Chapter; +import com.umc.product.organization.domain.ChapterSchool; +import com.umc.product.organization.domain.School; +import java.util.List; +import java.util.Optional; + +public interface LoadChapterSchoolPort { + + Optional findById(Long id); + + Optional findByChapterAndSchool(Chapter chapter, School school); + + List findAllByChapter(Chapter chapter); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java b/src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java new file mode 100644 index 00000000..fff29b19 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java @@ -0,0 +1,12 @@ +package com.umc.product.organization.application.port.out.query; + +import com.umc.product.organization.domain.Gisu; +import java.util.List; +import java.util.Optional; + +public interface LoadGisuPort { + + Optional findById(Long id); + + List findAll(); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java b/src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java new file mode 100644 index 00000000..1dc7fc27 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java @@ -0,0 +1,15 @@ +package com.umc.product.organization.application.port.out.query; + +import com.umc.product.organization.domain.Chapter; +import com.umc.product.organization.domain.School; +import java.util.List; +import java.util.Optional; + +public interface LoadSchoolPort { + + Optional findById(Long id); + + List findAll(); + + List findByChapter(Chapter chapter); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java b/src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java new file mode 100644 index 00000000..d3707f57 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java @@ -0,0 +1,13 @@ +package com.umc.product.organization.application.port.out.query; + +import com.umc.product.organization.domain.StudyGroup; +import com.umc.product.organization.domain.StudyGroupMember; +import java.util.List; +import java.util.Optional; + +public interface LoadStudyGroupMemberPort { + + Optional findById(Long id); + + List findByStudyGroup(StudyGroup studyGroup); +} diff --git a/src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java b/src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java new file mode 100644 index 00000000..048d9a47 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java @@ -0,0 +1,11 @@ +package com.umc.product.organization.application.port.out.query; + +import com.umc.product.organization.domain.StudyGroup; +import java.util.Optional; + +public interface LoadStudyGroupPort { + + Optional findById(Long id); + + Optional findByName(String name); +} diff --git a/src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java b/src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java new file mode 100644 index 00000000..f8e980d5 --- /dev/null +++ b/src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java @@ -0,0 +1,26 @@ +package com.umc.product.organization.application.port.service.command; + +import com.umc.product.organization.application.port.in.command.ManageSchoolUseCase; +import com.umc.product.organization.application.port.in.command.dto.CreateSchoolCommand; +import com.umc.product.organization.application.port.in.command.dto.UpdateSchoolCommand; +import org.springframework.stereotype.Service; + +@Service +public class SchoolService implements ManageSchoolUseCase { + + public void register(CreateSchoolCommand command) { + + } + + public void updateSchool(UpdateSchoolCommand command) { + + } + + public void deleteSchool(Long schoolId) { + + } + + public void deleteSchools(java.util.List schoolIds) { + + } +} diff --git a/src/main/java/com/umc/product/organization/domain/CentralOrganization.java b/src/main/java/com/umc/product/organization/domain/CentralOrganization.java new file mode 100644 index 00000000..a16016d9 --- /dev/null +++ b/src/main/java/com/umc/product/organization/domain/CentralOrganization.java @@ -0,0 +1,49 @@ +package com.umc.product.organization.domain; + +import com.umc.product.global.exception.BusinessException; +import com.umc.product.global.exception.constant.Domain; +import com.umc.product.organization.exception.OrganizationErrorCode; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CentralOrganization { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "gisu_id") + private Gisu gisu; + + private String name; + + @Builder + private CentralOrganization(Gisu gisu, String name) { + validate(gisu, name); + this.gisu = gisu; + this.name = name; + } + + private static void validate(Gisu gisu, String name) { + if (gisu == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.GISU_REQUIRED); + } + if (name == null || name.isBlank()) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.ORGAN_NAME_REQUIRED); + } + } + +} diff --git a/src/main/java/com/umc/product/organization/domain/Chapter.java b/src/main/java/com/umc/product/organization/domain/Chapter.java new file mode 100644 index 00000000..d945b893 --- /dev/null +++ b/src/main/java/com/umc/product/organization/domain/Chapter.java @@ -0,0 +1,48 @@ +package com.umc.product.organization.domain; + +import com.umc.product.global.exception.BusinessException; +import com.umc.product.global.exception.constant.Domain; +import com.umc.product.organization.exception.OrganizationErrorCode; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Chapter { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "gisu_id") + private Gisu gisu; + + private String name; + + @Builder + private Chapter(Gisu gisu, String name) { + validate(gisu, name); + this.gisu = gisu; + this.name = name; + } + + private static void validate(Gisu gisu, String name) { + if (gisu == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.GISU_REQUIRED); + } + if (name == null || name.isBlank()) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.ORGAN_NAME_REQUIRED); + } + } +} diff --git a/src/main/java/com/umc/product/organization/domain/ChapterSchool.java b/src/main/java/com/umc/product/organization/domain/ChapterSchool.java new file mode 100644 index 00000000..e7a25b5a --- /dev/null +++ b/src/main/java/com/umc/product/organization/domain/ChapterSchool.java @@ -0,0 +1,50 @@ +package com.umc.product.organization.domain; + +import com.umc.product.global.exception.BusinessException; +import com.umc.product.global.exception.constant.Domain; +import com.umc.product.organization.exception.OrganizationErrorCode; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ChapterSchool { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chapter_id") + private Chapter chapter; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "school_id") + private School school; + + @Builder + private ChapterSchool(Chapter chapter, School school) { + validate(chapter, school); + this.chapter = chapter; + this.school = school; + } + + private static void validate(Chapter chapter, School school) { + if (chapter == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.CHAPTER_REQUIRED); + } + if (school == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_REQUIRED); + } + } +} diff --git a/src/main/java/com/umc/product/organization/domain/Gisu.java b/src/main/java/com/umc/product/organization/domain/Gisu.java new file mode 100644 index 00000000..31248b86 --- /dev/null +++ b/src/main/java/com/umc/product/organization/domain/Gisu.java @@ -0,0 +1,65 @@ +package com.umc.product.organization.domain; + +import com.umc.product.global.exception.BusinessException; +import com.umc.product.global.exception.constant.Domain; +import com.umc.product.organization.exception.OrganizationErrorCode; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Gisu { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Long number; + + private boolean isActive; + + private LocalDateTime startAt; + + private LocalDateTime endAt; + + @Builder + private Gisu(Long number, boolean isActive, LocalDateTime startAt, LocalDateTime endAt) { + validate(startAt, endAt); + this.number = number; + this.isActive = isActive; + this.startAt = startAt; + this.endAt = endAt; + } + + private static void validate(LocalDateTime startAt, LocalDateTime endAt) { + if (startAt == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.GISU_START_AT_REQUIRED); + } + if (endAt == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.GISU_END_AT_REQUIRED); + } + if (!startAt.isBefore(endAt)) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.GISU_PERIOD_INVALID); + } + } + + public void active() { + this.isActive = true; + } + + public void inactive() { + this.isActive = false; + } + + public boolean isInPeriod(LocalDateTime now) { + return (now.isEqual(startAt) || now.isAfter(startAt)) && now.isBefore(endAt); + } +} diff --git a/src/main/java/com/umc/product/organization/domain/School.java b/src/main/java/com/umc/product/organization/domain/School.java new file mode 100644 index 00000000..92e57b64 --- /dev/null +++ b/src/main/java/com/umc/product/organization/domain/School.java @@ -0,0 +1,66 @@ +package com.umc.product.organization.domain; + +import com.umc.product.global.exception.BusinessException; +import com.umc.product.global.exception.constant.Domain; +import com.umc.product.organization.exception.OrganizationErrorCode; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class School { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + private String emailDomain; + + private String logoImageUrl; + + @Builder + private School(String name, String domain, String logoImageUrl) { + validate(name, domain); + this.name = name; + this.emailDomain = domain; + this.logoImageUrl = logoImageUrl; + } + + private static void validate(String name, String domain) { + if (name == null || name.isBlank()) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_NAME_REQUIRED); + } + if (domain == null || domain.isBlank()) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_DOMAIN_REQUIRED); + } + } + + public void updateLogoImageId(Long logoImageId) { + if (StringUtils.isEmpty(logoImageUrl)) { + this.logoImageUrl = logoImageUrl; + } + } + + public void updateName(String name) { + if (StringUtils.isEmpty(name)) { + this.name = name; + } + } + + public void updateEmailDomain(String emailDomain) { + if (StringUtils.isEmpty(emailDomain)) { + this.emailDomain = emailDomain; + } + } + +} diff --git a/src/main/java/com/umc/product/organization/domain/StudyGroup.java b/src/main/java/com/umc/product/organization/domain/StudyGroup.java new file mode 100644 index 00000000..d613c635 --- /dev/null +++ b/src/main/java/com/umc/product/organization/domain/StudyGroup.java @@ -0,0 +1,146 @@ +package com.umc.product.organization.domain; + +import com.umc.product.global.exception.BusinessException; +import com.umc.product.global.exception.constant.Domain; +import com.umc.product.organization.exception.OrganizationErrorCode; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class StudyGroup { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "gisu_id") + private Gisu gisu; + +// @ManyToOne(fetch = FetchType.LAZY) +// @JoinColumn(name = "group_leader_id") +// private Challenger challenger; + + @OneToMany(mappedBy = "studyGroup", cascade = CascadeType.ALL, orphanRemoval = true) + private List studyGroupMembers = new ArrayList<>(); + + @Builder + private StudyGroup(String name, Gisu gisu) { + validate(name, gisu); + this.name = name; + this.gisu = gisu; + } + + private static void validate(String name, Gisu gisu) { + if (name == null || name.isBlank()) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.STUDY_GROUP_NAME_REQUIRED); + } + if (gisu == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.GISU_REQUIRED); + } + } + + // ============ Domain Methods (Aggregate Root Pattern) ============ + + /** + * 스터디 그룹에 멤버 추가 + * + * @param challengerId 추가할 챌린저 ID + * @throws BusinessException 이미 존재하는 멤버인 경우 + */ + public void addMember(Long challengerId) { + validateMemberNotExists(challengerId); + StudyGroupMember member = StudyGroupMember.builder() + .studyGroup(this) + .challengerId(challengerId) + .build(); + studyGroupMembers.add(member); + } + + /** + * 스터디 그룹에서 멤버 제거 + * + * @param challengerId 제거할 챌린저 ID + * @throws BusinessException 멤버를 찾을 수 없는 경우 + */ + public void removeMember(Long challengerId) { + StudyGroupMember member = findMemberByChallengerId(challengerId); + studyGroupMembers.remove(member); + } + + /** + * 스터디 그룹 멤버 목록 전체 교체 + * + * @param challengerIds 새로운 멤버 ID 목록 + */ + public void updateMembers(List challengerIds) { + studyGroupMembers.clear(); + if (challengerIds != null) { + challengerIds.forEach(this::addMember); + } + } + + /** + * 스터디 그룹 이름 변경 + * + * @param newName 새로운 이름 + */ + public void updateName(String newName) { + if (newName == null || newName.isBlank()) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.STUDY_GROUP_NAME_REQUIRED); + } + this.name = newName; + } + + /** + * 특정 챌린저가 이 그룹의 멤버인지 확인 + * + * @param challengerId 확인할 챌린저 ID + * @return 멤버 여부 + */ + public boolean hasMember(Long challengerId) { + return studyGroupMembers.stream() + .anyMatch(member -> member.getChallengerId().equals(challengerId)); + } + + /** + * 멤버 수 조회 + * + * @return 현재 멤버 수 + */ + public int getMemberCount() { + return studyGroupMembers.size(); + } + + private void validateMemberNotExists(Long challengerId) { + if (hasMember(challengerId)) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.STUDY_GROUP_MEMBER_ALREADY_EXISTS); + } + } + + private StudyGroupMember findMemberByChallengerId(Long challengerId) { + return studyGroupMembers.stream() + .filter(member -> member.getChallengerId().equals(challengerId)) + .findFirst() + .orElseThrow( + () -> new BusinessException(Domain.COMMON, OrganizationErrorCode.STUDY_GROUP_MEMBER_NOT_FOUND)); + } + +} diff --git a/src/main/java/com/umc/product/organization/domain/StudyGroupMember.java b/src/main/java/com/umc/product/organization/domain/StudyGroupMember.java new file mode 100644 index 00000000..d7a32ac7 --- /dev/null +++ b/src/main/java/com/umc/product/organization/domain/StudyGroupMember.java @@ -0,0 +1,51 @@ +package com.umc.product.organization.domain; + +import com.umc.product.global.exception.BusinessException; +import com.umc.product.global.exception.constant.Domain; +import com.umc.product.organization.exception.OrganizationErrorCode; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class StudyGroupMember { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "group_id") + private StudyGroup studyGroup; + + // DDD 원칙: 다른 도메인(challenger)의 Entity를 직접 참조하지 않고 ID만 저장 + @Column(nullable = false) + private Long challengerId; + + @Builder + private StudyGroupMember(StudyGroup studyGroup, Long challengerId) { + validate(studyGroup, challengerId); + this.studyGroup = studyGroup; + this.challengerId = challengerId; + } + + private static void validate(StudyGroup studyGroup, Long challengerId) { + if (studyGroup == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.STUDY_GROUP_REQUIRED); + } + if (challengerId == null) { + throw new BusinessException(Domain.COMMON, OrganizationErrorCode.CHALLENGER_ID_REQUIRED); + } + } +} diff --git a/src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java b/src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java new file mode 100644 index 00000000..47e46720 --- /dev/null +++ b/src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java @@ -0,0 +1,37 @@ +package com.umc.product.organization.exception; + +import com.umc.product.global.response.code.BaseCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum OrganizationErrorCode implements BaseCode { + + GISU_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0001", "기수는 필수입니다."), + ORGAN_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0002", "조직 이름 설정은 필수입니다."), + SCHOOL_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0003", "학교는 필수입니다."), + CHAPTER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0004", "지부는 필수입니다."), + + + GISU_START_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0005", "기수 시작일은 필수입니다."), + GISU_END_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0006", "기수 종료일은 필수입니다."), + GISU_PERIOD_INVALID(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0007", "기수 시작일은 종료일보다 이전이어야 합니다."), + + SCHOOL_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0008", "학교 이름은 필수입니다."), + SCHOOL_DOMAIN_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0009", "학교 이메일 도메인은 필수입니다."), + + STUDY_GROUP_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0010", "스터디 그룹 이름은 필수입니다."), + STUDY_GROUP_LEADER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0011", "스터디 그룹 리더는 필수입니다."), + + STUDY_GROUP_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0012", "스터디 그룹은 필수입니다."), + STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0013", "스터디 그룹 멤버는 필수입니다."), + CHALLENGER_ID_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0014", "챌린저 ID는 필수입니다."), + STUDY_GROUP_MEMBER_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0015", "이미 존재하는 스터디 그룹 멤버입니다."), + STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");; + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} diff --git a/src/test/java/com/umc/product/organization/adapter/in/web/ChapterControllerTest.java b/src/test/java/com/umc/product/organization/adapter/in/web/ChapterControllerTest.java new file mode 100644 index 00000000..f802a009 --- /dev/null +++ b/src/test/java/com/umc/product/organization/adapter/in/web/ChapterControllerTest.java @@ -0,0 +1,36 @@ +package com.umc.product.organization.adapter.in.web; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.umc.product.organization.adapter.in.web.dto.request.CreateChapterRequest; +import com.umc.product.support.DocumentationTest; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class ChapterControllerTest extends DocumentationTest { + + @Test + void 신규_지부를_생성한다() throws Exception { + // given + CreateChapterRequest request = new CreateChapterRequest(1L, "Scorpio"); + + given(manageChapterUseCase.create(any())).willReturn(1L); + + // when + ResultActions result = mockMvc.perform( + post("/api/v1/admin/chapters").content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)); + + // then + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + requestFields(fieldWithPath("gisuId").type(JsonFieldType.STRING).description("기수 ID"), + fieldWithPath("name").type(JsonFieldType.STRING).description("지부명")))); + } +} diff --git a/src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java b/src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java new file mode 100644 index 00000000..24629d0a --- /dev/null +++ b/src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java @@ -0,0 +1,45 @@ +package com.umc.product.organization.adapter.in.web; + +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.umc.product.organization.application.port.in.query.dto.ChapterInfo; +import com.umc.product.support.DocumentationTest; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class ChapterQueryControllerTest extends DocumentationTest { + + @Test + void 지부_목록을_조회합니다() throws Exception { + // given + List chapters = List.of( + new ChapterInfo(1L, "Scorpio 지부"), + new ChapterInfo(2L, "Ain 지부"), + new ChapterInfo(3L, "Leo 지부") + ); + + given(getChapterUseCase.getAllChapter()).willReturn(chapters); + + // when + ResultActions result = mockMvc.perform(get("/api/v1/admin/chapters")); + + // then + result.andExpect(status().isOk()) + .andDo(restDocsHandler.document( + responseFields( + fieldWithPath("success").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result.chapters").type(JsonFieldType.ARRAY).description("지부 목록"), + fieldWithPath("result.chapters[].id").type(JsonFieldType.STRING).description("지부 ID"), + fieldWithPath("result.chapters[].name").type(JsonFieldType.STRING).description("지부 이름") + ) + )); + } +} diff --git a/src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java b/src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java new file mode 100644 index 00000000..f9d49ae8 --- /dev/null +++ b/src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java @@ -0,0 +1,87 @@ +package com.umc.product.organization.adapter.in.web; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +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.umc.product.organization.adapter.in.web.dto.request.CreateGisuRequest; +import com.umc.product.organization.adapter.in.web.dto.request.UpdateGisuRequest; +import com.umc.product.support.DocumentationTest; +import java.time.LocalDate; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class GisuControllerTest extends DocumentationTest { + + @Test + void 신규_기수를_추가한다() throws Exception { + // given + CreateGisuRequest request = new CreateGisuRequest(9L, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 31)); + + given(manageGisuUseCase.register(any())).willReturn(1L); + + // when + ResultActions result = mockMvc.perform( + post("/api/v1/admin/gisu").content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)); + + // then + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + requestFields(fieldWithPath("number").type(JsonFieldType.STRING).description("기수 번호"), + fieldWithPath("startAt").type(JsonFieldType.STRING).description("기수 시작일시"), + fieldWithPath("endAt").type(JsonFieldType.STRING).description("기수 종료일시")))); + } + + @Test + void 기수_정보를_수정한다() throws Exception { + // given + Long gisuId = 1L; + UpdateGisuRequest request = new UpdateGisuRequest(LocalDate.of(2025, 3, 1), LocalDate.of(2025, 9, 30)); + + // when + ResultActions result = mockMvc.perform( + patch("/api/v1/admin/gisu/{gisuId}", gisuId).content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)); + + // then + result.andExpect(status().isOk()) + .andDo(restDocsHandler.document(pathParameters(parameterWithName("gisuId").description("기수 ID")), + requestFields(fieldWithPath("startAt").type(JsonFieldType.STRING).description("기수 시작일시"), + fieldWithPath("endAt").type(JsonFieldType.STRING).description("기수 종료일시")))); + } + + @Test + void 기수를_삭제한다() throws Exception { + // given + Long gisuId = 1L; + + // when + ResultActions result = mockMvc.perform(delete("/api/v1/admin/gisu/{gisuId}", gisuId)); + + // then + result.andExpect(status().isOk()) + .andDo(restDocsHandler.document(pathParameters(parameterWithName("gisuId").description("기수 ID")))); + } + + @Test + void 현재_기수를_설정한다() throws Exception { + // given + Long gisuId = 3L; + + // when + ResultActions result = mockMvc.perform(post("/api/v1/admin/gisu/{gisuId}/current", gisuId)); + + // then + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + pathParameters(parameterWithName("gisuId").description("현재 기수로 설정할 기수 ID")))); + } +} diff --git a/src/test/java/com/umc/product/organization/adapter/in/web/GisuQueryControllerTest.java b/src/test/java/com/umc/product/organization/adapter/in/web/GisuQueryControllerTest.java new file mode 100644 index 00000000..c65bb4e1 --- /dev/null +++ b/src/test/java/com/umc/product/organization/adapter/in/web/GisuQueryControllerTest.java @@ -0,0 +1,45 @@ +package com.umc.product.organization.adapter.in.web; + +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.umc.product.organization.application.port.in.query.dto.GisuInfo; +import com.umc.product.support.DocumentationTest; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class GisuQueryControllerTest extends DocumentationTest { + + @Test + void 기수_목록을_조회한다() throws Exception { + // given + List gisuList = List.of( + new GisuInfo(1L, 7L, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 8, 31), false), + new GisuInfo(2L, 8L, LocalDate.of(2024, 9, 1), LocalDate.of(2025, 2, 28), false), + new GisuInfo(3L, 9L, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 31), true)); + + given(getGisuUseCase.getList()).willReturn(gisuList); + + // when + ResultActions result = mockMvc.perform(get("/api/v1/admin/gisu")); + + // then + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + responseFields(fieldWithPath("success").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result.gisuList").type(JsonFieldType.ARRAY).description("기수 목록"), + fieldWithPath("result.gisuList[].gisuId").type(JsonFieldType.STRING).description("기수 ID"), + fieldWithPath("result.gisuList[].number").type(JsonFieldType.STRING).description("기수 번호"), + fieldWithPath("result.gisuList[].startAt").type(JsonFieldType.STRING).description("기수 시작일시"), + fieldWithPath("result.gisuList[].endAt").type(JsonFieldType.STRING).description("기수 종료일시"), + fieldWithPath("result.gisuList[].isActive").type(JsonFieldType.BOOLEAN) + .description("현재 기수 여부")))); + } +} diff --git a/src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java b/src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java new file mode 100644 index 00000000..2c60ec6d --- /dev/null +++ b/src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java @@ -0,0 +1,95 @@ +package com.umc.product.organization.adapter.in.web; + +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +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.umc.product.organization.adapter.in.web.dto.request.CreateSchoolRequest; +import com.umc.product.organization.adapter.in.web.dto.request.DeleteSchoolsRequest; +import com.umc.product.support.DocumentationTest; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +public class SchoolControllerTest extends DocumentationTest { + + + @Test + void 총괄_신규학교를_추가한다() throws Exception { + // given when + CreateSchoolRequest request = CreateSchoolRequest.builder().schoolName("중앙대학교").chapterId("3") + .remark("중앙대는 멋집니다.").build(); + + // then + ResultActions result = mockMvc.perform( + post("/api/v1/admin/schools").content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)); + + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + requestFields(fieldWithPath("schoolName").type(JsonFieldType.STRING).description("학교 이름"), + fieldWithPath("chapterId").type(JsonFieldType.STRING).description("소속 지부 ID"), + fieldWithPath("remark").type(JsonFieldType.STRING).description("비고")))); + + } + + @Test + void 총괄_학교정보를_수정한다() throws Exception { + // given // when + Long schoolId = 1L; + + CreateSchoolRequest request = CreateSchoolRequest.builder().schoolName("동국대학교").chapterId("3") + .remark("신승호 라면이 맛있습니다.").build(); + + ResultActions result = mockMvc.perform( + patch("/api/v1/admin/schools/{schoolId}", schoolId).content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)); + + // then + result.andExpect(status().isOk()) + .andDo(restDocsHandler.document(pathParameters(parameterWithName("schoolId").description("학교 ID")), + requestFields( + fieldWithPath("schoolName").optional().type(JsonFieldType.STRING).description("학교 이름"), + fieldWithPath("chapterId").optional().type(JsonFieldType.STRING) + .description("소속 지부 ID"), + fieldWithPath("remark").optional().type(JsonFieldType.STRING).description("비고")))); + + } + + @Test + void 총괄_학교를_제거한다() throws Exception { + // given // when + Long schoolId = 1L; + + ResultActions result = mockMvc.perform(delete("/api/v1/admin/schools/{schoolId}", schoolId)); + + // then + result.andExpect(status().isOk()) + .andDo(restDocsHandler.document(pathParameters(parameterWithName("schoolId").description("학교 ID") + + ))); + + } + + @Test + void 총괄_학교를_일괄_삭제한다() throws Exception { + // given + DeleteSchoolsRequest request = new DeleteSchoolsRequest(List.of(1L, 2L, 3L)); + + // when + ResultActions result = mockMvc.perform( + delete("/api/v1/admin/schools").content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)); + + // then + result.andExpect(status().isOk()).andDo(restDocsHandler.document( + requestFields(fieldWithPath("schoolIds").type(JsonFieldType.ARRAY).description("삭제할 학교 ID 목록")))); + } + +} diff --git a/src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java b/src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java new file mode 100644 index 00000000..403d124d --- /dev/null +++ b/src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java @@ -0,0 +1,121 @@ +package com.umc.product.organization.adapter.in.web; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +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.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.umc.product.organization.application.port.in.query.dto.SchoolInfo; +import com.umc.product.organization.application.port.in.query.dto.SchoolListItemInfo; +import com.umc.product.support.DocumentationTest; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +class SchoolQueryControllerTest extends DocumentationTest { + + @Test + void 학교_목록을_조회합니다() throws Exception { + // given + int page = 0; + int size = 10; + + List items = List.of( + new SchoolListItemInfo(1L, "서울대학교", 1L, "Ain 지부", LocalDate.of(2025, 12, 31), true), + new SchoolListItemInfo(2L, "연세대학교", 1L, "Ain 지부", LocalDate.of(2025, 12, 29), true), + new SchoolListItemInfo(3L, "고려대학교", 1L, "Ain 지부", LocalDate.of(2025, 12, 30), false) + ); + + Page pageResult = new PageImpl<>( + items, + PageRequest.of(page, size), + 3L + ); + + given(getSchoolUseCase.getList(any(), any())).willReturn(pageResult); + + // when + ResultActions result = mockMvc.perform( + get("/api/v1/admin/schools") + .param("keyword", "중앙대학교") + .param("chapterId", "1") + .param("page", "0") + .param("size", String.valueOf(size)) + ); + + // then + result.andExpect(status().isOk()) + .andDo(restDocsHandler.document( + queryParameters( + parameterWithName("keyword").description("학교 이름 검색 키워드").optional(), + parameterWithName("chapterId").description("지부 ID").optional(), + parameterWithName("page").description("페이지 번호 (0부터 시작)").optional(), + parameterWithName("size").description("페이지 당 조회 수").optional() + ), + responseFields( + fieldWithPath("success").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result.content").type(JsonFieldType.ARRAY).description("학교 목록"), + fieldWithPath("result.content[].schoolId").type(JsonFieldType.STRING) + .description("학교 ID"), + fieldWithPath("result.content[].schoolName").type(JsonFieldType.STRING) + .description("학교 이름"), + fieldWithPath("result.content[].chapterId").type(JsonFieldType.STRING) + .description("지부 ID"), + fieldWithPath("result.content[].chapterName").type(JsonFieldType.STRING) + .description("지부 이름"), + fieldWithPath("result.content[].createdAt").type(JsonFieldType.STRING) + .description("등록일"), + fieldWithPath("result.content[].isActive").type(JsonFieldType.BOOLEAN) + .description("활성 상태"), + fieldWithPath("result.page").type(JsonFieldType.STRING) + .description("현재 페이지 번호 (0부터 시작)"), + fieldWithPath("result.size").type(JsonFieldType.STRING).description("페이지 당 조회 수"), + fieldWithPath("result.totalElements").type(JsonFieldType.STRING).description("총 학교 수"), + fieldWithPath("result.totalPages").type(JsonFieldType.STRING).description("총 페이지 수"), + fieldWithPath("result.hasNext").type(JsonFieldType.BOOLEAN).description("다음 페이지 존재 여부"), + fieldWithPath("result.hasPrevious").type(JsonFieldType.BOOLEAN) + .description("이전 페이지 존재 여부") + ) + )); + } + + @Test + void 학교_상세정보를_조회합니다() throws Exception { + // give + Long schoolId = 1L; + LocalDate createdAt = LocalDate.of(2026, 1, 1); + LocalDate updatedAt = LocalDate.of(2026, 1, 4); + + SchoolInfo schoolInfo = new SchoolInfo(3L, "Ain 지부", "중앙대학교", 1L, "비고", createdAt, updatedAt); + + given(getSchoolUseCase.getSchoolDetail(schoolId)).willReturn(schoolInfo); + // when + ResultActions result = mockMvc.perform(get("/api/v1/admin/schools/{schoolId}", schoolId)); + // then + result.andExpect((status().isOk())) + .andDo(restDocsHandler.document(pathParameters(parameterWithName("schoolId").description("학교 ID")), + responseFields(fieldWithPath("success").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("result.chapterId").type(JsonFieldType.STRING).description("지부 ID"), + fieldWithPath("result.chapterName").type(JsonFieldType.STRING).description("지부 이름"), + fieldWithPath("result.schoolName").type(JsonFieldType.STRING).description("학교 이름"), + fieldWithPath("result.schoolId").type(JsonFieldType.STRING).description("학교 ID"), + fieldWithPath("result.remark").type(JsonFieldType.STRING).description("비고"), + fieldWithPath("result.createdAt").type(JsonFieldType.STRING).description("생성일자"), + fieldWithPath("result.updatedAt").type(JsonFieldType.STRING).description("수정일자")))); + } + +} diff --git a/src/test/java/com/umc/product/support/DocumentationTest.java b/src/test/java/com/umc/product/support/DocumentationTest.java index 05f430e5..739c6150 100644 --- a/src/test/java/com/umc/product/support/DocumentationTest.java +++ b/src/test/java/com/umc/product/support/DocumentationTest.java @@ -2,16 +2,36 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import com.umc.product.global.security.JwtTokenProvider; +import com.umc.product.organization.adapter.in.web.ChapterController; +import com.umc.product.organization.adapter.in.web.ChapterQueryController; +import com.umc.product.organization.adapter.in.web.GisuController; +import com.umc.product.organization.adapter.in.web.GisuQueryController; +import com.umc.product.organization.adapter.in.web.SchoolController; +import com.umc.product.organization.adapter.in.web.SchoolQueryController; +import com.umc.product.organization.application.port.in.command.ManageChapterUseCase; +import com.umc.product.organization.application.port.in.command.ManageGisuUseCase; +import com.umc.product.organization.application.port.in.command.ManageSchoolUseCase; +import com.umc.product.organization.application.port.in.query.GetChapterUseCase; +import com.umc.product.organization.application.port.in.query.GetGisuUseCase; +import com.umc.product.organization.application.port.in.query.GetSchoolUseCase; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.annotation.Import; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -@WebMvcTest( -) +@WebMvcTest(controllers = { + SchoolController.class, + SchoolQueryController.class, + ChapterController.class, + ChapterQueryController.class, + GisuController.class, + GisuQueryController.class, +}) @Import({ RestDocsConfig.class, }) @@ -19,6 +39,7 @@ @AutoConfigureRestDocs public class DocumentationTest { + @Autowired protected MockMvc mockMvc; @@ -28,4 +49,24 @@ public class DocumentationTest { @Autowired protected ObjectMapper objectMapper; + @MockitoBean + protected JwtTokenProvider jwtTokenProvider; + + @MockitoBean + protected ManageSchoolUseCase manageSchoolUseCase; + + @MockitoBean + protected GetSchoolUseCase getSchoolUseCase; + + @MockitoBean + protected ManageChapterUseCase manageChapterUseCase; + + @MockitoBean + protected GetChapterUseCase getChapterUseCase; + + @MockitoBean + protected ManageGisuUseCase manageGisuUseCase; + + @MockitoBean + protected GetGisuUseCase getGisuUseCase; }