Skip to content

feat: 어드민에서 출신 대학을 관리하는 기능 추가#725

Open
whqtker wants to merge 7 commits into
developfrom
feat/632-manage-home-univ-admin
Open

feat: 어드민에서 출신 대학을 관리하는 기능 추가#725
whqtker wants to merge 7 commits into
developfrom
feat/632-manage-home-univ-admin

Conversation

@whqtker
Copy link
Copy Markdown
Member

@whqtker whqtker commented May 29, 2026

관련 이슈

작업 내용

어드민에서 출신(협정) 대학을 추가하는 CRUD를 추가합니다.

특이 사항

리뷰 요구사항 (선택)

@whqtker whqtker self-assigned this May 29, 2026
@whqtker whqtker added the 기능 label May 29, 2026
@whqtker whqtker requested review from Gyuhyeok99 and wibaek as code owners May 29, 2026 11:22
@whqtker whqtker added the 최종 리뷰 최소 1명 필수 label May 29, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Review Change Stack

Walkthrough

이 PR은 협정 대학교에 대한 관리자용 CRUD 기능을 추가합니다. 생성/수정 요청 DTO와 응답 DTO를 정의하고, 협정 대학 관련 에러 코드 3가지(NOT_FOUND, ALREADY_EXISTS, HAS_REFERENCES)를 ErrorCode enum에 등록합니다. HomeUniversity 도메인에는 이름 갱신을 위한 update() 메서드를, 리포지토리 2곳(SiteUserRepository, UnivApplyInfoRepository)에는 협정 대학 참조 존재 여부를 확인하는 쿼리 메서드를 추가합니다. 서비스 계층에서는 CRUD 작업(조회, 생성, 수정, 삭제)을 구현하고 이름 중복 검증 및 참조 데이터 존재 검증을 수행합니다. REST 컨트롤러가 이들 서비스를 /admin/home-universities 엔드포인트로 노출하며, 237줄의 통합 테스트가 모든 CRUD 시나리오와 예외 케이스를 검증합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • wibaek
  • Hexeong
  • lsy1307
  • JAEHEE25
  • sukangpunch
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive 로고 이미지 처리 기능은 이슈에 명시되었으나 현재 PR에서는 구현되지 않았으므로 범위 확인이 필요합니다. 이슈 #632의 '로고 이미지(선택)' 요구사항에 대한 구현 여부를 명확히 하거나 향후 PR로 계획하세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경의 핵심인 어드민에서의 출신 대학 관리 기능을 명확하고 간결하게 전달합니다.
Description check ✅ Passed PR 설명이 연결된 이슈와 작업 내용의 필수 요소를 포함하며, 기본적인 템플릿 구조를 따릅니다.
Linked Issues check ✅ Passed PR의 코드 변경사항이 이슈 #632의 요구사항인 출신 대학 CRUD 기능을 완전히 구현합니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/632-manage-home-univ-admin

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 and usage tips.

@whqtker whqtker changed the title Feat/632 manage home univ admin feat: 어드민에서 출신 대학을 관리하는 기능 추가 May 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java (1)

82-96: ⚡ Quick win

중복 검증 로직을 하나로 합치면 더 깔끔해질 것 같아요.

validateNameNotExistsvalidateNameNotDuplicated는 사실상 같은 일을 하고 있고, excludeIdnull로 넘긴 validateNameNotDuplicated(name, null)validateNameNotExists(name)과 동일하게 동작합니다(existing.getId().equals(null)은 항상 false이므로 생성 시에도 의도대로 예외가 발생합니다). 하나로 통합하면 의미 중복을 줄이고 유지보수가 쉬워집니다.

♻️ 검증 메서드 통합 제안
-    private void validateNameNotExists(String name) {
-        homeUniversityRepository.findByName(name)
-                .ifPresent(existing -> {
-                    throw new CustomException(HOME_UNIVERSITY_ALREADY_EXISTS);
-                });
-    }
-
-    private void validateNameNotDuplicated(String name, Long excludeId) {
+    private void validateNameNotDuplicated(String name, Long excludeId) {
         homeUniversityRepository.findByName(name)
                 .ifPresent(existing -> {
                     if (!existing.getId().equals(excludeId)) {
                         throw new CustomException(HOME_UNIVERSITY_ALREADY_EXISTS);
                     }
                 });
     }

생성 시 호출부도 함께 변경합니다.

-        validateNameNotExists(request.name());
+        validateNameNotDuplicated(request.name(), null);
🤖 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/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java`
around lines 82 - 96, Combine the two validators into a single method (keep the
name validateNameNotDuplicated(String name, Long excludeId) or choose a clear
single name) that uses
homeUniversityRepository.findByName(name).ifPresent(existing -> { if (excludeId
== null || !existing.getId().equals(excludeId)) throw new
CustomException(HOME_UNIVERSITY_ALREADY_EXISTS); }); then replace all calls to
validateNameNotExists(name) with validateNameNotDuplicated(name, null) (or call
the new single method from both create and update flows) so duplicate-check
logic is centralized and correctly handles null excludeId.
src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java (3)

39-44: ⚡ Quick win

생성 성공 시 201 Created 상태 코드와 Location 헤더를 반환하는 것을 권장합니다.

현재 구현은 200 OK를 반환하지만, REST 모범 사례에 따르면 리소스 생성 시에는 201 Created 상태 코드를 사용하고 생성된 리소스의 URI를 Location 헤더에 포함해야 합니다. 이렇게 하면 API 사용자가 새로 생성된 리소스에 직접 접근할 수 있습니다.

♻️ 제안하는 개선 방안
+import org.springframework.http.HttpStatus;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import java.net.URI;
+
 `@PostMapping`
 public ResponseEntity<AdminHomeUniversityResponse> createHomeUniversity(
         `@Valid` `@RequestBody` AdminHomeUniversityCreateRequest request
 ) {
-    return ResponseEntity.ok(adminHomeUniversityService.createHomeUniversity(request));
+    AdminHomeUniversityResponse response = adminHomeUniversityService.createHomeUniversity(request);
+    URI location = ServletUriComponentsBuilder
+            .fromCurrentRequest()
+            .path("/{id}")
+            .buildAndExpand(response.id())
+            .toUri();
+    return ResponseEntity.created(location).body(response);
 }
🤖 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/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java`
around lines 39 - 44, The createHomeUniversity endpoint currently returns 200
OK; change it to return 201 Created with a Location header pointing to the new
resource URI. Update the createHomeUniversity controller method to call
adminHomeUniversityService.createHomeUniversity(request) to obtain the created
resource or its id, build the resource URI (e.g., using
ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(created.getId()).toUri()),
and return ResponseEntity.created(location).body(createdResponse) so the
response status is 201 and the Location header is set.

54-60: ⚡ Quick win

삭제 성공 시 204 No Content 상태 코드를 반환하는 것을 권장합니다.

현재 구현은 200 OK를 반환하지만, REST 모범 사례에 따르면 응답 본문이 없는 DELETE 요청은 204 No Content 상태 코드를 사용하는 것이 더 적절합니다.

♻️ 제안하는 개선 방안
 `@DeleteMapping`("/{home-university-id}")
 public ResponseEntity<Void> deleteHomeUniversity(
         `@PathVariable`("home-university-id") Long homeUniversityId
 ) {
     adminHomeUniversityService.deleteHomeUniversity(homeUniversityId);
-    return ResponseEntity.ok().build();
+    return ResponseEntity.noContent().build();
 }
🤖 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/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java`
around lines 54 - 60, Change the deleteHomeUniversity endpoint in
AdminHomeUniversityController to return 204 No Content instead of 200 OK: after
calling adminHomeUniversityService.deleteHomeUniversity(homeUniversityId) have
the method return ResponseEntity.noContent().build() (i.e., use
ResponseEntity.noContent() in the deleteHomeUniversity method).

20-23: 🏗️ Heavy lift

관리자 엔드포인트(/admin/)는 전역 보안 설정으로 이미 보호되고 있습니다.**

  1. src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController 의 라우트(/admin/home-universities)는 SecurityConfiguration에서 requestMatchers("/admin/**").hasRole(ADMIN.name())로 처리되어 관리자 역할 없이는 접근이 차단됩니다.
  2. 클래스/메서드 단에 @PreAuthorize를 추가하면 의도가 더 명확해지는 “방어적 가독성” 차원 개선으로 고려할 만합니다.
🤖 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/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java`
around lines 20 - 23, 현재 AdminHomeUniversityController 클래스(경로
"/admin/home-universities")는 SecurityConfiguration의
requestMatchers("/admin/**").hasRole(ADMIN.name())로 이미 보호되고 있으나 가독성·방어적 문서화를 위해
클래스나 핵심 메서드에 `@PreAuthorize`("hasRole('ADMIN')")를 추가하세요; 대상은
AdminHomeUniversityController 클래스 선언부 또는 개별 엔드포인트 메서드이며 SecurityConfiguration을
변경하지 말고 `@PreAuthorize` 어노테이션이 Spring Method Security가 활성화된 상태에서만 작동하도록 설정(예:
`@EnableMethodSecurity` 또는 `@EnableGlobalMethodSecurity`)되어 있는지 확인하세요.
🤖 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.

Nitpick comments:
In
`@src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java`:
- Around line 39-44: The createHomeUniversity endpoint currently returns 200 OK;
change it to return 201 Created with a Location header pointing to the new
resource URI. Update the createHomeUniversity controller method to call
adminHomeUniversityService.createHomeUniversity(request) to obtain the created
resource or its id, build the resource URI (e.g., using
ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(created.getId()).toUri()),
and return ResponseEntity.created(location).body(createdResponse) so the
response status is 201 and the Location header is set.
- Around line 54-60: Change the deleteHomeUniversity endpoint in
AdminHomeUniversityController to return 204 No Content instead of 200 OK: after
calling adminHomeUniversityService.deleteHomeUniversity(homeUniversityId) have
the method return ResponseEntity.noContent().build() (i.e., use
ResponseEntity.noContent() in the deleteHomeUniversity method).
- Around line 20-23: 현재 AdminHomeUniversityController 클래스(경로
"/admin/home-universities")는 SecurityConfiguration의
requestMatchers("/admin/**").hasRole(ADMIN.name())로 이미 보호되고 있으나 가독성·방어적 문서화를 위해
클래스나 핵심 메서드에 `@PreAuthorize`("hasRole('ADMIN')")를 추가하세요; 대상은
AdminHomeUniversityController 클래스 선언부 또는 개별 엔드포인트 메서드이며 SecurityConfiguration을
변경하지 말고 `@PreAuthorize` 어노테이션이 Spring Method Security가 활성화된 상태에서만 작동하도록 설정(예:
`@EnableMethodSecurity` 또는 `@EnableGlobalMethodSecurity`)되어 있는지 확인하세요.

In
`@src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java`:
- Around line 82-96: Combine the two validators into a single method (keep the
name validateNameNotDuplicated(String name, Long excludeId) or choose a clear
single name) that uses
homeUniversityRepository.findByName(name).ifPresent(existing -> { if (excludeId
== null || !existing.getId().equals(excludeId)) throw new
CustomException(HOME_UNIVERSITY_ALREADY_EXISTS); }); then replace all calls to
validateNameNotExists(name) with validateNameNotDuplicated(name, null) (or call
the new single method from both create and update flows) so duplicate-check
logic is centralized and correctly handles null excludeId.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7445e042-7163-4850-91b6-0a93255e5c60

📥 Commits

Reviewing files that changed from the base of the PR and between 67aad12 and ebd6094.

📒 Files selected for processing (10)
  • src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java
  • src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java
  • src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java
  • src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java
  • src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java
  • src/main/java/com/example/solidconnection/common/exception/ErrorCode.java
  • src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java
  • src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java
  • src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java
  • src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java

Copy link
Copy Markdown
Contributor

@Gyuhyeok99 Gyuhyeok99 left a comment

Choose a reason for hiding this comment

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

오랜만에 솔커 코드 보니 마음이 편안해지네요.. 너무 잘읽힙니다

import com.example.solidconnection.university.domain.HomeUniversity;

public record AdminHomeUniversityResponse(
Long id,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이런 건 null이될 수 없으니 long이 낫지 않나요? 머쓱하지만 오랜만에 pr봅니다..

void 저장된_모든_협정대학을_반환한다() {
// given
HomeUniversity homeUniversity1 = homeUniversityFixture.인하대학교();
HomeUniversity homeUniversity2 = homeUniversityRepository.save(new HomeUniversity(null, "연세대학교"));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

오랜만에 코드봐서 제가 바뀐 컨벤션을 모르지만 얜 fixture 안쓴 이유가 혹시 있나요?

Comment on lines +82 to +87
private void validateNameNotExists(String name) {
homeUniversityRepository.findByName(name)
.ifPresent(existing -> {
throw new CustomException(HOME_UNIVERSITY_ALREADY_EXISTS);
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이제 private 함수는 맨 밑으로 보내게 바뀌었나보군요!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

기능 최종 리뷰 최소 1명 필수

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 어드민에서 출신 대학을 관리하는 기능 추가

2 participants