Skip to content

[feat] 일반 구성원 추가 기능 구현#578

Merged
kangkings merged 9 commits into
devfrom
feat/518-create-member-semester
Jun 23, 2026
Merged

[feat] 일반 구성원 추가 기능 구현#578
kangkings merged 9 commits into
devfrom
feat/518-create-member-semester

Conversation

@kangkings

@kangkings kangkings commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

연관 이슈

Closes #518

작업 내용

  • 일반 구성원 추가 API 구현
  • 이메일 기준 기존 Member 조회 및 신규 생성
  • MemberActivity와 MemberSemester 일괄 생성
  • 동일 기수 활동 중복 등록 검증
  • 기수 및 teamId 유효성 검증
  • 관련 테스트 추가

호출 구조

Controller → UseCase(전체 로직 조립) → 도메인별 Service → Repository
AdminMemberSemesterController → AdminMemberUseCase → SemesterInquiryUsecase → SemesterRepository
AdminMemberSemesterController → AdminMemberUseCase → AdminMemberTeamService → TeamRepository
AdminMemberSemesterController → AdminMemberUseCase → AdminMemberService → MemberRepository
AdminMemberSemesterController → AdminMemberUseCase → AdminMemberActivityService → MemberActivityRepository

UseCase는 구성원 추가 흐름을 조립하고, 실제 조회,생성,저장 책임은 도메인별 Service에 분리했습니다.

MemberActivity는 일반 구성원 활동의 Aggregate Root로서 MemberSemester를 생성하며, 두 엔티티는 식별 관계와 Cascade를 통해 함께 저장됩니다.

MemberActivity.createSemesterActivity() → MemberSemester.create() → MemberActivityRepository.save()

API

일반 구성원 추가

POST /admin/members/semester

  • 'teamId'가 입력된 경우 해당 기수에 속한 팀인지 검증
  • 이메일에 해당하는 Member가 없으면 신상정보 생성
  • 기존 Member가 있으면 신상정보를 재사용하고 활동만 추가
  • 동일 기수 활동이 존재하면 중복 등록 예외 반환

Summary by CodeRabbit

릴리스 노트

New Features

  • 관리자용 회원 등록 API 엔드포인트 추가: 기수별로 회원을 일괄 등록할 수 있습니다. 이름, 이메일, 전화번호, 직군, 모집 유형, 경력, 관심 도메인, 거주 지역 등의 정보를 입력받으며, 필수 항목 검증과 이메일 형식 검증이 자동으로 적용됩니다.

Tests

  • 회원 등록 로직 및 저장소 관련 단위 테스트 추가

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kangkings, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 6 minutes and 13 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4cdbdea5-6fed-46c1-b00b-5cf7be76098b

📥 Commits

Reviewing files that changed from the base of the PR and between bee8134 and 1d07d98.

📒 Files selected for processing (6)
  • src/main/java/org/ject/support/admin/member/dto/request/CreateMemberSemesterRequest.java
  • src/main/java/org/ject/support/admin/member/service/AdminMemberService.java
  • src/main/java/org/ject/support/domain/member/entity/Member.java
  • src/main/java/org/ject/support/domain/member/repository/MemberRepository.java
  • src/test/java/org/ject/support/admin/member/service/AdminMemberServiceTest.java
  • src/test/java/org/ject/support/domain/member/repository/MemberRepositoryTest.java

Walkthrough

관리자가 일반 구성원을 특정 기수에 추가할 수 있는 POST /admin/members/semester 엔드포인트를 신규 도입한다. Member, MemberActivity, MemberSemester 도메인 엔티티에 정적 팩토리 메서드를 추가하고, 이메일 기반 findOrCreate, 팀 소속 검증, 기수 활동 중복 검증을 담당하는 서비스 계층과 단위/통합 테스트 전체를 함께 추가한다.

Changes

Admin Member Semester 생성 기능

Layer / File(s) Summary
요청 DTO 및 검증 계약
src/main/java/org/ject/support/admin/member/dto/request/CreateMemberSemesterRequest.java
이름·이메일·전화번호·직군·모집단위·기수ID 등 입력 필드에 Jakarta Validation 어노테이션(@NotBlank, @Email, @Pattern, @Size, @NotNull)을 선언한 record DTO를 신규 정의한다.
도메인 엔티티 팩토리 메서드 및 에러코드
src/main/java/org/ject/support/domain/member/entity/Member.java, MemberActivity.java, MemberSemester.java, src/main/java/org/ject/support/domain/member/exception/MemberErrorCode.java
Member, MemberActivity, MemberSemester@BuilderPRIVATE로 제한하고 각 엔티티에 create / createSemesterActivity 정적 팩토리 메서드를 추가한다. MemberActivity@OneToOne memberSemester 필드가 추가되고, ALREADY_EXIST_MEMBER_SEMESTER_ACTIVITY(CONFLICT)와 NOT_FOUND_TEAM_OF_SEMESTER(NOT_FOUND) 에러코드가 신설된다.
레포지토리 확장 및 SemesterInquiry 구현
src/main/java/org/ject/support/domain/member/repository/MemberActivityRepository.java, MemberRepository.java, src/main/java/org/ject/support/domain/member/repository/TeamRepository.java, src/main/java/org/ject/support/domain/recruit/service/SemesterInquiryUsecase.java, SemesterInquiryService.java
MemberActivityRepository(신규, existsSemesterActivity JPQL)와 findByEmail, findIdsBySemesterId 쿼리 메서드를 추가하고, SemesterInquiryUsecasegetSemester(Long id) 계약을 선언 후 SemesterInquiryService에 구현한다.
Admin 서비스 계층 및 UseCase 오케스트레이션
src/main/java/org/ject/support/admin/member/service/AdminMemberService.java, AdminMemberTeamService.java, AdminMemberActivityService.java, AdminMemberUseCase.java
AdminMemberService(이메일 기반 findOrCreate), AdminMemberTeamService(팀 ID 조회), AdminMemberActivityService(중복 검증→생성→저장)를 구현하고, AdminMemberUseCase가 트랜잭션 내에서 기수 존재 확인, 팀 소속 검증, Member 생성/조회, MemberActivity 저장을 순서대로 위임한다.
REST 엔드포인트 및 Swagger 명세
src/main/java/org/ject/support/admin/member/controller/AdminMemberSemesterApiSpec.java, AdminMemberSemesterController.java
AdminMemberSemesterApiSpec 인터페이스에 Swagger 메타데이터와 @Valid 검증을 선언하고, AdminMemberSemesterControllerPOST /admin/members/semester를 구현해 AdminMemberUseCase로 위임한다.
테스트 픽스처 및 도메인 엔티티 단위 테스트
src/test/java/org/ject/support/domain/member/fixture/MemberFixture.java, SemesterActivityFixture.java, src/test/java/org/ject/support/domain/member/entity/MemberTest.java, MemberActivityTest.java, src/test/java/org/ject/support/common/data/redis/resilience/CacheFallbackIntegrationTest.java, src/test/java/org/ject/support/domain/file/controller/FileControllerTest.java, src/test/java/org/ject/support/domain/recruit/controller/QuestionControllerTest.java
MemberFixture·SemesterActivityFixture 플루언트 빌더를 신규 추가하고, MemberTest·MemberActivityTest로 팩토리 메서드 동작을 검증한다. 기존 테스트의 Member.builder() 직접 사용을 MemberFixture 기반으로 교체한다.
서비스·레포지토리·UseCase 단위/통합 테스트
src/test/java/org/ject/support/admin/member/.../*Test.java, src/test/java/org/ject/support/domain/member/repository/*RepositoryTest.java, src/test/java/org/ject/support/domain/recruit/service/SemesterInquiryServiceTest.java
AdminMemberServiceTest, AdminMemberTeamServiceTest, AdminMemberActivityServiceTest, AdminMemberUseCaseTest, AdminMemberSemesterControllerTest(MockMvc) 단위 테스트와 MemberRepositoryTest, MemberActivityRepositoryTest, TeamRepositoryTest JPA 테스트, SemesterInquiryServiceTest로 정상 흐름·예외 경로를 검증한다.

Sequence Diagram(s)

sequenceDiagram
  participant Client as 관리자 클라이언트
  participant Controller as AdminMemberSemesterController
  participant UseCase as AdminMemberUseCase
  participant SemesterInquiry as SemesterInquiryUsecase
  participant TeamService as AdminMemberTeamService
  participant MemberService as AdminMemberService
  participant ActivityService as AdminMemberActivityService

  rect rgba(100, 149, 237, 0.5)
    note over Client,Controller: POST /admin/members/semester
    Client->>Controller: createAdminMemberSemester(request)
    Controller->>UseCase: createMemberSemester(request)
  end

  rect rgba(144, 238, 144, 0.5)
    note over UseCase,SemesterInquiry: 기수 존재 검증
    UseCase->>SemesterInquiry: getSemester(semesterId)
    SemesterInquiry-->>UseCase: SemesterResponse
  end

  rect rgba(255, 200, 100, 0.5)
    note over UseCase,TeamService: 팀 소속 검증 (teamId != null 시)
    UseCase->>TeamService: getTeamIdsBySemesterId(semesterId)
    TeamService-->>UseCase: List teamIds
    UseCase-->>UseCase: teamId 포함 여부 확인
  end

  rect rgba(255, 160, 122, 0.5)
    note over UseCase,ActivityService: Member 조회/생성 및 활동 저장
    UseCase->>MemberService: findOrCreateMember(request)
    MemberService-->>UseCase: memberId
    UseCase->>ActivityService: createMemberSemesterActivity(request, memberId)
    ActivityService-->>UseCase: void
  end

  UseCase-->>Controller: void
  Controller-->>Client: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 토끼가 기수를 등록하네,
팩토리 메서드로 엔티티 뚝딱 🏗️
이메일 찾아 없으면 새로 만들고,
팀 소속도 꼼꼼히 검증하며 hop~
테스트까지 촘촘히 짜놓으니,
구성원 추가, 이제 걱정 없다! 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 '[feat] 일반 구성원 추가 기능 구현'은 pull request의 주요 변경사항인 일반 구성원 추가 기능을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed PR은 링크된 이슈 #518의 '일반 구성원 추가' 기능 요구사항을 충족합니다. 이메일 기반 회원 조회/생성, 활동 및 세메스터 추적, 검증 로직이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed PR의 모든 변경사항은 일반 구성원 추가 기능과 관련되어 있으며, 테스트 지원 인프라(MemberFixture, SemesterActivityFixture) 추가도 해당 기능 테스트를 위한 필요한 범위 내 변경입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/518-create-member-semester

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kangkings kangkings self-assigned this Jun 22, 2026
@kangkings
kangkings requested review from 023-dev and fivedragon5 June 22, 2026 16:05
@kangkings kangkings added ✨feature 구현, 개선 사항 관련 부분 👨🏻‍💻backend 백엔드 작업 labels Jun 22, 2026
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Test Results

376 tests   376 ✅  14s ⏱️
 76 suites    0 💤
 76 files      0 ❌

Results for commit 1d07d98.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

📊테스트 커버리지

Overall Project 84.02% 🍏
Files changed 100% 🍏

File Coverage
SemesterInquiryService.java 100% 🍏
AdminMemberService.java 100% 🍏
AdminMemberActivityService.java 100% 🍏
AdminMemberUseCase.java 100% 🍏
AdminMemberTeamService.java 100% 🍏
Member.java 100% 🍏
MemberSemester.java 100% 🍏
MemberActivity.java 100% 🍏

@kangkings
kangkings marked this pull request as ready for review June 23, 2026 05:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/test/java/org/ject/support/admin/member/controller/AdminMemberSemesterControllerTest.java (1)

101-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

유효성 실패 응답의 code 필드도 함께 검증해주세요.

Line 105, Line 121은 HTTP 400만 확인하고 있어 에러 응답 포맷 회귀를 놓칠 수 있습니다. $.code까지 단언하면 GlobalExceptionHandler 계약을 더 안정적으로 고정할 수 있습니다.

테스트 보강 예시
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import org.ject.support.common.exception.GlobalErrorCode;
...
         mockMvc.perform(post("/admin/members/semester")
                 .contentType(MediaType.APPLICATION_JSON)
                 .content(objectMapper.writeValueAsString(request)))
-            .andExpect(status().isBadRequest());
+            .andExpect(status().isBadRequest())
+            .andExpect(jsonPath("$.code").value(GlobalErrorCode.METHOD_VALIDATION_FAILED.getCode()));
...
         mockMvc.perform(post("/admin/members/semester")
                 .contentType(MediaType.APPLICATION_JSON)
                 .content(objectMapper.writeValueAsString(request)))
-            .andExpect(status().isBadRequest());
+            .andExpect(status().isBadRequest())
+            .andExpect(jsonPath("$.code").value(GlobalErrorCode.METHOD_VALIDATION_FAILED.getCode()));

Based on learnings, 이 저장소의 예외 응답 코드는 $.code 경로로 검증해야 합니다 ($.data.code 아님).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/org/ject/support/admin/member/controller/AdminMemberSemesterControllerTest.java`
around lines 101 - 125, The test methods around the validation failure cases
(with post request to /admin/members/semester endpoint at lines 101-107 and
113-125) are only verifying the HTTP 400 status but not validating the response
code field. Add an additional assertion using andExpect(jsonPath("$.code")...)
before each andExpect(status().isBadRequest()) to verify that the response
includes the proper error code, ensuring the GlobalExceptionHandler contract is
properly validated and preventing future regressions in error response format.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/org/ject/support/admin/member/dto/request/CreateMemberSemesterRequest.java`:
- Around line 23-33: The request DTO CreateMemberSemesterRequest is missing
validation annotations that match the constraints defined in the Member and
MemberActivity entities. Add size/length validations to the name field to
enforce the maximum length constraint, add a pattern validation to the
phoneNumber field to match the expected format constraint, and add size/length
validation to the memo field at line 51 to match the entity's maximum length
constraint. This ensures invalid data is rejected at the request boundary rather
than causing exceptions during entity persistence.

In
`@src/main/java/org/ject/support/admin/member/service/AdminMemberActivityService.java`:
- Around line 21-44: The createMemberSemesterActivity method has a race
condition where validateDuplicateSemesterActivity check and
memberActivityRepository.save can be bypassed by concurrent requests that both
pass validation. Keep the service-level validateDuplicateSemesterActivity check
in place, but additionally handle DataIntegrityViolationException from the
memberActivityRepository.save call and map it to throw MemberException with
ALREADY_EXIST_MEMBER_SEMESTER_ACTIVITY. This requires adding a unique constraint
at the database level for the semester activity combination to make the
duplicate prevention atomically safe.

In `@src/main/java/org/ject/support/admin/member/service/AdminMemberService.java`:
- Around line 17-35: The findOrCreateMember method has a race condition where
concurrent requests with the same email can both pass the findByEmail check and
create duplicate members. Add a database unique constraint on the email column
for the Member entity to prevent duplicates at the database level. Then modify
the createMember method to catch the constraint violation exception
(DataIntegrityViolationException) that occurs when a concurrent request already
saved a member with that email. When this exception is caught, query the
memberRepository again with findByEmail to get the member that was created by
the concurrent request and return that member's ID instead of propagating the
exception.

In
`@src/main/java/org/ject/support/domain/member/repository/MemberRepository.java`:
- Line 10: The DataIntegrityViolationException thrown when attempting to save a
member with a duplicate email (especially when that email belongs to a
soft-deleted record) is not being caught and handled properly. Either catch the
DataIntegrityViolationException in the createMember() method within
AdminMemberService and convert it to a MemberException with
MemberErrorCode.DUPLICATE_EMAIL, or add a dedicated exception handler for
DataIntegrityViolationException in GlobalExceptionHandler that returns an
appropriate error response. The MemberErrorCode.DUPLICATE_EMAIL constant already
exists and should be utilized to provide a proper error message instead of
exposing the raw 500 error to users.

---

Nitpick comments:
In
`@src/test/java/org/ject/support/admin/member/controller/AdminMemberSemesterControllerTest.java`:
- Around line 101-125: The test methods around the validation failure cases
(with post request to /admin/members/semester endpoint at lines 101-107 and
113-125) are only verifying the HTTP 400 status but not validating the response
code field. Add an additional assertion using andExpect(jsonPath("$.code")...)
before each andExpect(status().isBadRequest()) to verify that the response
includes the proper error code, ensuring the GlobalExceptionHandler contract is
properly validated and preventing future regressions in error response format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c387ac18-1891-4558-83ba-b18c4b018cb5

📥 Commits

Reviewing files that changed from the base of the PR and between cc6a1bc and bee8134.

📒 Files selected for processing (32)
  • src/main/java/org/ject/support/admin/member/controller/AdminMemberSemesterApiSpec.java
  • src/main/java/org/ject/support/admin/member/controller/AdminMemberSemesterController.java
  • src/main/java/org/ject/support/admin/member/dto/request/CreateMemberSemesterRequest.java
  • src/main/java/org/ject/support/admin/member/service/AdminMemberActivityService.java
  • src/main/java/org/ject/support/admin/member/service/AdminMemberService.java
  • src/main/java/org/ject/support/admin/member/service/AdminMemberTeamService.java
  • src/main/java/org/ject/support/admin/member/service/AdminMemberUseCase.java
  • src/main/java/org/ject/support/domain/member/entity/Member.java
  • src/main/java/org/ject/support/domain/member/entity/MemberActivity.java
  • src/main/java/org/ject/support/domain/member/entity/MemberSemester.java
  • src/main/java/org/ject/support/domain/member/exception/MemberErrorCode.java
  • src/main/java/org/ject/support/domain/member/repository/MemberActivityRepository.java
  • src/main/java/org/ject/support/domain/member/repository/MemberRepository.java
  • src/main/java/org/ject/support/domain/member/repository/TeamRepository.java
  • src/main/java/org/ject/support/domain/recruit/service/SemesterInquiryService.java
  • src/main/java/org/ject/support/domain/recruit/service/SemesterInquiryUsecase.java
  • src/test/java/org/ject/support/admin/member/controller/AdminMemberSemesterControllerTest.java
  • src/test/java/org/ject/support/admin/member/service/AdminMemberActivityServiceTest.java
  • src/test/java/org/ject/support/admin/member/service/AdminMemberServiceTest.java
  • src/test/java/org/ject/support/admin/member/service/AdminMemberTeamServiceTest.java
  • src/test/java/org/ject/support/admin/member/service/AdminMemberUseCaseTest.java
  • src/test/java/org/ject/support/common/data/redis/resilience/CacheFallbackIntegrationTest.java
  • src/test/java/org/ject/support/domain/file/controller/FileControllerTest.java
  • src/test/java/org/ject/support/domain/member/entity/MemberActivityTest.java
  • src/test/java/org/ject/support/domain/member/entity/MemberTest.java
  • src/test/java/org/ject/support/domain/member/fixture/MemberFixture.java
  • src/test/java/org/ject/support/domain/member/fixture/SemesterActivityFixture.java
  • src/test/java/org/ject/support/domain/member/repository/MemberActivityRepositoryTest.java
  • src/test/java/org/ject/support/domain/member/repository/MemberRepositoryTest.java
  • src/test/java/org/ject/support/domain/member/repository/TeamRepositoryTest.java
  • src/test/java/org/ject/support/domain/recruit/controller/QuestionControllerTest.java
  • src/test/java/org/ject/support/domain/recruit/service/SemesterInquiryServiceTest.java

@kangkings
kangkings merged commit ff59fa9 into dev Jun 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👨🏻‍💻backend 백엔드 작업 ✨feature 구현, 개선 사항 관련 부분

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FUNC_MBR_MGT_03] 일반 구성원 추가

2 participants