Skip to content

merge(#41) 봉사 조회 및 역할 지정 api 추가#44

Open
ByunDohwi wants to merge 27 commits intodevelopfrom
feat/(#41)-봉사-조회-및-역할-지정-api-추가

Hidden character warning

The head ref may contain hidden characters: "feat/(#41)-\ubd09\uc0ac-\uc870\ud68c-\ubc0f-\uc5ed\ud560-\uc9c0\uc815-api-\ucd94\uac00"
Open

merge(#41) 봉사 조회 및 역할 지정 api 추가#44
ByunDohwi wants to merge 27 commits intodevelopfrom
feat/(#41)-봉사-조회-및-역할-지정-api-추가

Conversation

@ByunDohwi
Copy link
Copy Markdown
Member

@ByunDohwi ByunDohwi commented Apr 2, 2026

✨ 리뷰 시 참고 사항을 작성해주세요

리뷰 시 참고할 점이 있다면 작성해주세요.


👩‍💻 작업한 내용을 설명해주세요

학생 역할 부여, 봉사 상세 조회, 봉사 리스트 조회 api 추가하였습니다.


📸 결과물을 캡쳐해주세요

결과 화면을 첨부해주세요.

image image

🔗 관련 이슈 번호를 첨부하여주세요

#41

Summary by CodeRabbit

  • 새로운 기능

    • 자원봉사자 상세 조회 추가(일정·참여·활동·사용자 이름 포함)
    • 자원봉사자 목록 조회 추가(상태·연도 필터 및 정렬 지원)
    • 사용자 활동 일괄 생성용 POST /activities/user API 추가
    • 외부 인증 연동으로 사용자 이름 조회 지원(미조회 시 에러 처리)
  • Chores

    • gRPC/Protobuf 통신 및 빌드 설정 추가(프로토 정의·플러그인)
    • DB 마이그레이션 추가(사용자 ID 컬럼 및 제약/인덱스 적용)

@ByunDohwi ByunDohwi self-assigned this Apr 2, 2026
@ByunDohwi ByunDohwi added the feat label Apr 2, 2026
@ByunDohwi ByunDohwi linked an issue Apr 2, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

프로토콜버퍼·gRPC 설정과 auth.proto 추가, 자원봉사자 상세·목록 조회 및 사용자-활동 생성 관련 엔드포인트·서비스·퍼시스턴스·매퍼·리포지토리, DB 마이그레이션이 다수 추가/수정되었습니다.

Changes

Cohort / File(s) Summary
빌드·프로토콜버퍼·gRPC
finda-volunteer/build.gradle.kts, finda-volunteer/src/main/proto/auth.proto, finda-volunteer/src/main/resources/application.yml
protobuf 플러그인·protoc·gRPC 코드생성 및 런타임 의존성 추가, auth.proto 추가, grpc.client.auth-service 구성 추가.
웹 어댑터: 활동
finda-volunteer/src/main/kotlin/.../adapter/in/activity/ActivityWebAdapter.kt, .../dto/request/CreateUserActivityRequest.kt, .../mapper/CreateUserActivityRequestMapper.kt
POST /activities/user 핸들러 추가, 요청 DTO 및 요청→커맨드 매퍼 추가.
웹 어댑터: 자원봉사자 및 DTO
.../adapter/in/volunteer/VolunteerWebAdapter.kt, .../dto/response/VolunteerDetailResponse.kt, .../dto/response/VolunteerListResponse.kt, .../mapper/VolunteerResultMapper.kt
자원봉사자 상세·목록 GET 핸들러 및 결과→응답 매핑 추가.
외부 gRPC 어댑터
finda-volunteer/src/main/kotlin/.../adapter/out/grpc/UserGrpcAdapter.kt
Auth gRPC 클라이언트 주입(@GrpcClient) 및 getUserName(UUID) 구현(데드라인·NOT_FOUND 처리·로깅).
퍼시스턴스: 활동 관련
.../adapter/out/persistence/activity/ActivityPersistenceAdapter.kt, .../UserActivityAdapter.kt, .../entity/UserActivityJpaEntity.kt, .../mapper/UserActivityMapper.kt, .../repository/ActivityRepository.kt, .../repository/UserActivityRepository.kt
user_id 엔티티/DB 컬럼 추가, UserActivity 어댑터·매퍼 추가, 조인/조회용 리포지토리 메서드 추가.
퍼시스턴스: 자원봉사자 관련
.../adapter/out/persistence/volunteer/VolunteerDetailPersistenceAdapter.kt, .../VolunteerPersistenceAdapter.kt, .../repository/...
VolunteerDetail 집계 조회 어댑터 추가, 리스트 포트 구현, 다수의 volunteerId 기반 조회 메서드 추가.
애플리케이션 포트·서비스
.../application/port/in/*, .../application/port/out/*, .../service/*
새 인바운드/아웃바운드 포트 추가 및 CreateUserActivity/VolunteerDetail/VolunteerList 서비스 구현(매핑·예외 처리 포함).
도메인·유틸 변경
.../domain/activity/model/UserActivity.kt, .../domain/volunteer/model/VolunteerDetail.kt, .../domain/volunteer/enum/VolunteerSortBy.kt
도메인 UserActivityuserId 추가, VolunteerDetail 집계 모델 및 VolunteerSortBy enum·변환 로직 추가.
예외·에러코드
.../application/exception/InvalidVolunteerSortByException.kt, .../application/exception/UserNotFoundException.kt, .../global/error/exception/ErrorCode.kt
InvalidVolunteerSortByException, UserNotFoundException 추가 및 ErrorCode 확장.
DB 마이그레이션·리소스
finda-volunteer/src/main/resources/db/changelog-volunteer/master.xml, .../schema/*.sql
마스터 체인지로그에 두 변경셋 추가, 인덱스명 수정, user_id 컬럼 추가 및 교사 참여 유니크 제약 추가.
기타
.gitignore
환경 파일·스크립트 패턴 추가.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant VolunteerWebAdapter as "VolunteerWebAdapter"
    participant VolunteerDetailService as "VolunteerDetailService"
    participant VolunteerDetailPort as "VolunteerDetailQueryPort"
    participant UserQueryPort as "UserQueryPort"
    participant AuthGRPC as "Auth Service (gRPC)"
    participant Database as "Database"

    Client->>VolunteerWebAdapter: GET /volunteers/{volunteerId}
    VolunteerWebAdapter->>VolunteerDetailService: execute(volunteerId)
    VolunteerDetailService->>VolunteerDetailPort: findDetailByIdOrThrow(volunteerId)
    VolunteerDetailPort->>Database: 조회 (volunteer, schedules, participations, activities, userActivities, recurrences)
    Database-->>VolunteerDetailPort: VolunteerDetail
    VolunteerDetailPort-->>VolunteerDetailService: VolunteerDetail

    loop 각 userId에 대해
        VolunteerDetailService->>UserQueryPort: getUserName(userId)
        UserQueryPort->>AuthGRPC: GetUserName(UserRequest)
        AuthGRPC-->>UserQueryPort: UserNameResponse
        UserQueryPort-->>VolunteerDetailService: userName or null
    end

    VolunteerDetailService-->>VolunteerWebAdapter: VolunteerDetailResponse
    VolunteerWebAdapter-->>Client: 200 OK
Loading
sequenceDiagram
    actor Client
    participant ActivityWebAdapter as "ActivityWebAdapter"
    participant CreateUserActivityService as "CreateUserActivityService"
    participant ActivityQueryPort as "ActivityQueryPort"
    participant UserActivityCommand as "UserActivityCommandPort"
    participant Database as "Database"

    Client->>ActivityWebAdapter: POST /activities/user (CreateUserActivityRequest)
    ActivityWebAdapter->>CreateUserActivityService: execute(request.toCommand())

    loop 각 userActivity 항목
        CreateUserActivityService->>ActivityQueryPort: findById(activityId)
        ActivityQueryPort->>Database: activity 조회
        Database-->>ActivityQueryPort: Activity
        ActivityQueryPort-->>CreateUserActivityService: Activity

        CreateUserActivityService->>UserActivityCommand: save(UserActivity(userId, activityId))
        UserActivityCommand->>Database: insert tbl_user_activity (user_id, activity_id, ...)
        Database-->>UserActivityCommand: 저장된 엔티티
        UserActivityCommand-->>CreateUserActivityService: UserActivity
    end

    CreateUserActivityService-->>ActivityWebAdapter: 완료
    ActivityWebAdapter-->>Client: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • pji-min

"나는 토끼, 깡충 뛰며 알리네,
프로토콜로 길 닦고 gRPC로 묻네,
이름을 물어 답을 받고, 활동을 쌓았네,
자원봉사자들 모여 춤을 추네,
당근 들고 축하해요 🥕🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경 사항을 정확하게 설명합니다. 봉사 조회 API(상세 조회, 리스트 조회) 추가와 역할 지정 API 추가라는 핵심 내용이 명확히 드러나 있습니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)

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.

class ActivityWebAdapter(
private val createUserActivityUseCase: CreateUserActivityUseCase
) {
@PostMapping("/user")
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.

ResponseStatus 추가하시면 좋을 것 같아요

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@pji-min pji-min self-requested a review April 2, 2026 02:25
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.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/mapper/UserActivityMapper.kt (1)

25-32: ⚠️ Potential issue | 🔴 Critical

mapper에서 activityId 존재 검증 필요

CreateUserActivityServiceactivity!!.id로 activity 존재성을 검증한 후 UserActivity를 생성합니다. 그러나 toEntity 메서드에서는 activityRepository.findByIdOrNull(domain.activityId)null을 반환할 수 있으며, 이 경우 activity = null인 엔티티가 저장됩니다. 이후 toDomain 메서드의 requireNotNull(entity.activity?.id)에서 IllegalArgumentException이 발생합니다.

service 계층의 검증 계약을 준수하기 위해 toEntity에서도 activity 존재성을 명시적으로 검증해야 합니다:

수정 제안
override fun toEntity(domain: UserActivity): UserActivityJpaEntity {
    val activity = requireNotNull(activityRepository.findByIdOrNull(domain.activityId)) {
        "Activity not found for id: ${domain.activityId}"
    }
    
    return UserActivityJpaEntity(
        id = domain.id,
        userId = domain.userId,
        activity = activity
    )
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/mapper/UserActivityMapper.kt`
around lines 25 - 32, The toEntity method currently uses
activityRepository.findByIdOrNull(domain.activityId) and can return null,
allowing a UserActivityJpaEntity with a null activity; update toEntity in
UserActivityMapper to explicitly validate presence of the Activity (use
requireNotNull or throw a clear exception) when resolving
activityRepository.findByIdOrNull(domain.activityId) and only construct
UserActivityJpaEntity with a non-null activity, mirroring the service contract
enforced before creating UserActivity and preventing downstream requireNotNull
failures in toDomain.
🧹 Nitpick comments (7)
finda-volunteer/src/main/resources/db/changelog-volunteer/schema/001-create-volunteer-tables.sql (1)

74-75: 외래 키 제약 조건 이름의 오타도 함께 수정하는 것이 좋습니다.

인덱스 이름을 idx_student_participation_volunteer_id로 수정했지만, 74번 라인의 외래 키 제약 조건 이름 fk_student_particitation_volunteer에는 여전히 동일한 오타(particitation)가 남아 있습니다. 일관성을 위해 함께 수정을 고려해 주세요.

♻️ 외래 키 제약 조건 이름 수정 제안
-    CONSTRAINT fk_student_particitation_volunteer
+    CONSTRAINT fk_student_participation_volunteer
         FOREIGN KEY (volunteer_id) REFERENCES tbl_volunteer (id) ON DELETE CASCADE

Also applies to: 145-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/resources/db/changelog-volunteer/schema/001-create-volunteer-tables.sql`
around lines 74 - 75, The foreign key constraint name contains a typo: rename
fk_student_particitation_volunteer to fk_student_participation_volunteer to
match the corrected index name idx_student_participation_volunteer_id; update
all occurrences of fk_student_particitation_volunteer (including the second
occurrence noted around the other block) so the CONSTRAINT declaration (FOREIGN
KEY (volunteer_id) REFERENCES tbl_volunteer (id) ON DELETE CASCADE) uses the
corrected constraint name consistently.
finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/repository/VolunteerRepository.kt (1)

24-27: 정렬 방향의 유연성을 고려해 보세요.

현재 메서드는 workStartDate DESC로 고정되어 있습니다. PR 이미지에 sort_by 쿼리 파라미터가 보이는데, 향후 다양한 정렬 옵션(오름차순/내림차순)이 필요하다면 별도의 메서드 추가나 동적 쿼리 구현을 고려해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/repository/VolunteerRepository.kt`
around lines 24 - 27, 메서드 findAllByStatusAndYearOrderByWorkStartDateDesc가
workStartDate DESC로 정렬을 고정하고 있어 정렬 방향이 유연하지 않습니다; VolunteerRepository에 정렬 파라미터를
받도록 시그니처를 변경하거나 스프링 데이터의 Sort/Pageable을 사용하여 동적 정렬을 지원하세요 — 예를 들어 메서드 이름에서
OrderBy를 제거하고 findAllByStatusAndYear(status, year, sort: Sort) 또는
findAllByStatusAndYear(status, year, pageable: Pageable) 형태로 바꿔 컨트롤러에서 쿼리
파라미터(sort_by, direction)를 받아 적절한 Sort 객체를 전달하도록 구현하면 됩니다.
finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/VolunteerPersistenceAdapter.kt (1)

59-69: FQN 대신 import 사용 권장

Line 60에서 VolunteerStatus에 대해 전체 패키지 경로(FQN)를 사용하고 있습니다. 일관성을 위해 상단에 import를 추가하고 클래스명만 사용하는 것이 좋습니다.

♻️ 제안된 수정

import 추가:

import finda.findavolunteer.domain.volunteer.enum.VolunteerStatus
     override fun findAll(
-        status: finda.findavolunteer.domain.volunteer.enum.VolunteerStatus?,
+        status: VolunteerStatus?,
         year: Int?,
         sortBy: VolunteerSortBy
     ): List<Volunteer> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/VolunteerPersistenceAdapter.kt`
around lines 59 - 69, The parameter type uses the fully-qualified name for
VolunteerStatus in VolunteerPersistenceAdapter.findAll; replace the FQN with an
import by adding "import
finda.findavolunteer.domain.volunteer.enum.VolunteerStatus" at the top and
change the method signature to use VolunteerStatus directly (affecting the
signature of findAll and any references like
volunteerRepository.findAllByStatusAndYearOrderByWorkStartDateDesc and
volunteerMapper::toDomain).
finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerDetailService.kt (2)

48-68: 사용자 이름 조회 시 N+1 문제 발생 가능

studentParticipationuserActivity에 대해 개별적으로 userQueryPort.getUserName(userId)을 호출하고 있습니다. 참여자가 많을 경우 다수의 gRPC 호출이 발생하여 응답 시간이 증가할 수 있습니다.

배치 조회 메서드(예: getUserNames(userIds: List<UUID>))를 도입하여 한 번의 호출로 모든 사용자 이름을 가져오는 것을 고려해보세요.

♻️ 개선 방향 예시
// UserQueryPort에 배치 메서드 추가
fun getUserNames(userIds: List<UUID>): Map<UUID, String>

// VolunteerDetailService에서 사용
val allUserIds = (volunteerDetail.studentParticipations.map { it.userId } +
                  volunteerDetail.userActivities.map { it.userId }).distinct()
val userNameMap = userQueryPort.getUserNames(allUserIds)

// 매핑 시 Map에서 조회
studentParticipations = volunteerDetail.studentParticipations.map {
    StudentParticipationResponse(
        userId = it.userId,
        name = userNameMap[it.userId] ?: throw UserNotFoundException,
        // ...
    )
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerDetailService.kt`
around lines 48 - 68, The code in VolunteerDetailService calls
userQueryPort.getUserName for each volunteerDetail.studentParticipations and
volunteerDetail.userActivities causing potential N+1 gRPC calls; add a batch
lookup on UserQueryPort (e.g., getUserNames(userIds: List<UUID>): Map<UUID,
String>), collect distinct userIds from volunteerDetail.studentParticipations
and volunteerDetail.userActivities, call the new batch method once to get a
userNameMap, and replace per-item getUserName calls with lookups like
userNameMap[it.userId] ?: throw UserNotFoundException to populate
StudentParticipationResponse and UserActivityResponse.

20-70: 읽기 전용 트랜잭션 누락 확인

서비스 메서드에 @Transactional(readOnly = true) 어노테이션이 없습니다. 읽기 전용 트랜잭션을 명시하면 JPA 더티 체킹을 비활성화하여 성능이 향상될 수 있습니다.

♻️ 제안된 수정
+import org.springframework.transaction.annotation.Transactional
+
 `@Service`
 class VolunteerDetailService(
     private val volunteerDetailQueryPort: VolunteerDetailQueryPort,
     private val userQueryPort: UserQueryPort
 ) : VolunteerDetailUseCase {
+    `@Transactional`(readOnly = true)
     override fun execute(volunteerId: UUID): VolunteerDetailResponse {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerDetailService.kt`
around lines 20 - 70, Add a read-only transaction to the volunteer detail
retrieval: annotate the VolunteerDetailService.execute method (or the
VolunteerDetailService class) with `@Transactional`(readOnly = true) and import
org.springframework.transaction.annotation.Transactional so JPA dirty checking
is disabled for this read operation; ensure the annotation is placed on the
overridden execute(volunteerId: UUID) function (or class) that calls
volunteerDetailQueryPort.findDetailByIdOrThrow and userQueryPort.getUserName.
finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/VolunteerDetailPersistenceAdapter.kt (1)

32-105: 여러 개의 별도 Repository 호출로 인한 성능 고려

하나의 봉사 상세 조회에 7개의 Repository 호출이 발생합니다. 현재 구현은 기능적으로 정확하지만, 트래픽이 증가하면 단일 쿼리 또는 배치 조회로 최적화를 고려해볼 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/VolunteerDetailPersistenceAdapter.kt`
around lines 32 - 105, findDetailByIdOrThrow currently issues seven separate
repository calls (volunteerRepository, volunteerScheduleRepository,
studentParticipationRepository, activityRepository, userActivityRepository,
activityRecurrenceWeekRepository, activityRecurrenceMonthRepository), which will
hurt performance under load; refactor by consolidating these into fewer queries
— e.g., add a custom query or repository method (on VolunteerRepository or a new
VolunteerDetailQueryRepository) that fetches the Volunteer and its related
collections via JOIN FETCH/entity-graph or returns a projection/DTO mapping to
VolunteerDetail, or use batched IN queries and a mapper to assemble
VolunteerDetail in one pass; keep the public API (findDetailByIdOrThrow) but
replace the multiple repository calls with the new consolidated query method and
adapt mapping of Volunteer, schedules, activities, userActivities, weekdays,
monthDate and studentParticipations accordingly.
finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/activity/CreateUserActivityService.kt (1)

21-25: it.activityId를 직접 사용 가능

activity.id는 이미 it.activityId와 동일한 값입니다. Activity 엔티티 조회는 존재 검증 목적으로만 사용되고 있으므로, 조회 후 activity.id 대신 it.activityId를 직접 사용할 수 있습니다.

♻️ 제안된 수정
         request.userActivityList.forEach {
-            val activity = activityQueryPort.findById(it.activityId)
+            activityQueryPort.findById(it.activityId)
+                ?: throw ActivityNotFoundException
             userActivityCommandPort.save(
                 UserActivity(
-                    activityId = activity!!.id,
+                    activityId = it.activityId,
                     userId = it.userId
                 )
             )
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/activity/CreateUserActivityService.kt`
around lines 21 - 25, UserActivity 생성 시 activity!!.id 대신 it.activityId를 직접 사용하도록
변경하세요: UserActivity(activityId = it.activityId, userId = it.userId). Activity
엔티티 조회는 여전히 존재 검증용으로 유지하되, 생성자에는 조회된 activity의 id를 사용하지 않아 널 강제(!!)를 제거하고
it.activityId로 대체해 안전하게 처리하세요 (참조: CreateUserActivityService, UserActivity,
activity, it.activityId).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/VolunteerWebAdapter.kt`:
- Around line 50-51: 삭제 엔드포인트인 VolunteerWebAdapter의 deleteVolunteer 메서드는
`@ResponseStatus`(HttpStatus.NO_CONTENT) 어노테이션이 빠져 기본 응답코드가 200으로 바뀌었습니다;
deleteVolunteer(`@RequestParam` volunteerId: UUID) 메서드에
`@ResponseStatus`(HttpStatus.NO_CONTENT) 어노테이션을 복원하여 기존 API 계약(204 NO_CONTENT)을
유지하고, 기존 호출 흐름인 deleteVolunteerUseCase.execute(volunteerId)를 변경하지 마세요.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/grpc/UserGrpcAdapter.kt`:
- Around line 35-36: In UserGrpcAdapter (around the log.error("gRPC getUserName
failed. userId=$userId", e) and subsequent throw e), avoid logging the raw
userId; instead compute a non-reversible or masked representation (e.g., SHA-256
hash or mask middle characters) and log that token (e.g., hashedUserId or
maskedUserId) in the error message, keeping the same context and rethrowing the
exception unchanged; update the log call to use the sanitized identifier
variable rather than userId.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerDetailUseCase.kt`:
- Around line 3-7: The VolunteerDetailUseCase interface currently depends on an
adapter-layer DTO (VolunteerDetailResponse) which inverts layer dependencies;
move the DTO from adapter.`in`.volunteer.dto.response to the application layer
(e.g., application.port.in.volunteer.dto) and update the import in
VolunteerDetailUseCase to reference the new package, then update the adapter
implementation(s) and any mappers to construct/convert to the new
application-layer VolunteerDetailResponse so only the adapter depends on the
application contract (keep the interface signature fun execute(volunteerId:
UUID): VolunteerDetailResponse unchanged).

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerListUseCase.kt`:
- Around line 3-11: The VolunteerListUseCase interface currently returns the
web-layer DTO VolunteerListResponse (remove the import and dependency) — change
the execute(...) signature to return an application/domain model (e.g.,
VolunteerSummary or VolunteerDto defined in the application/domain package)
instead of VolunteerListResponse, and keep parameters (status, year, sortBy)
unchanged; then implement mapping from that domain/application model to
adapter.in.volunteer.dto.response.VolunteerListResponse inside the web adapter
(controller/mapper) so the port no longer depends on adapter types and
VolunteerListUseCase remains layer-pure.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/activity/CreateUserActivityService.kt`:
- Around line 17-27: The loop in CreateUserActivityService.execute uses
activityQueryPort.findById and then activity!!.id which causes a
KotlinNullPointerException when not found; replace this with a null-safe lookup
that throws a clear "not found" error per convention: call
activityQueryPort.findByIdOrThrow(it.activityId) if that method exists, or
perform an explicit null check on the result of
activityQueryPort.findById(it.activityId) and throw a domain NotFoundException
(or similar) with a descriptive message before constructing UserActivity and
calling userActivityCommandPort.save(UserActivity(...)).

In `@finda-volunteer/src/main/proto/auth.proto`:
- Line 3: The package declaration "package auth;" in auth.proto doesn't match
the file's directory layout and triggers Buf's PACKAGE_DIRECTORY_MATCH error;
fix it by either moving auth.proto into a directory named auth so the path
matches the package, or by updating the package declaration to the correct
package name that reflects the current directory structure (replace "package
auth;" accordingly) and update any import/option references that depend on that
package.

In `@finda-volunteer/src/main/resources/application.yml`:
- Around line 7-11: The default gRPC client negotiation-type is set to plaintext
for auth-service in application.yml, which enables non-TLS traffic in
production; update the configuration so production enforces TLS by either
changing the default negotiation-type under grpc.client.auth-service to TLS or
make negotiation-type configurable via an environment variable (e.g.
AUTH_GRPC_NEGOTIATION_TYPE) and ensure application-prod.yml explicitly sets
grpc.client.auth-service.negotiation-type: TLS to force TLS/mTLS in production;
locate the negotiation-type setting in application.yml and add/adjust the
corresponding override in application-prod.yml or read from the env var.

In
`@finda-volunteer/src/main/resources/db/changelog-volunteer/schema/002-add-user-id-to-user-activity.sql`:
- Around line 4-5: The migration should not add user_id as NOT NULL in one step;
instead modify the changelog to add the column as nullable first on
tbl_user_activity (ADD COLUMN user_id BINARY(16) NULL), perform a separate
backfill step that updates tbl_user_activity.user_id with correct values (or a
default/mapped value) in a follow-up changeSet, and finally add a third
changeSet that alters the column to NOT NULL (ALTER TABLE tbl_user_activity
MODIFY COLUMN user_id BINARY(16) NOT NULL) after verifying no NULLs remain;
reference the table name tbl_user_activity and column user_id when implementing
these staged changeSets.

In
`@finda-volunteer/src/main/resources/db/changelog-volunteer/schema/003-add-unique-teacher-participation-user-id.sql`:
- Around line 4-5: The migration adds a UNIQUE(user_id) constraint
(uk_teacher_participation_user_id) on tbl_teacher_participation which conflicts
with the repository design (TeacherParticipationRepository.findAllByUserId
returning List<TeacherParticipationJpaEntity>) and prevents a teacher from
having multiple participation records; update the migration to either drop this
UNIQUE constraint or replace it with a composite unique on (user_id,
volunteer_id) to match tbl_student_participation semantics and allow multiple
participations per user, ensuring the constraint name
(uk_teacher_participation_user_id) and table (tbl_teacher_participation) are
adjusted accordingly.

---

Outside diff comments:
In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/mapper/UserActivityMapper.kt`:
- Around line 25-32: The toEntity method currently uses
activityRepository.findByIdOrNull(domain.activityId) and can return null,
allowing a UserActivityJpaEntity with a null activity; update toEntity in
UserActivityMapper to explicitly validate presence of the Activity (use
requireNotNull or throw a clear exception) when resolving
activityRepository.findByIdOrNull(domain.activityId) and only construct
UserActivityJpaEntity with a non-null activity, mirroring the service contract
enforced before creating UserActivity and preventing downstream requireNotNull
failures in toDomain.

---

Nitpick comments:
In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/repository/VolunteerRepository.kt`:
- Around line 24-27: 메서드 findAllByStatusAndYearOrderByWorkStartDateDesc가
workStartDate DESC로 정렬을 고정하고 있어 정렬 방향이 유연하지 않습니다; VolunteerRepository에 정렬 파라미터를
받도록 시그니처를 변경하거나 스프링 데이터의 Sort/Pageable을 사용하여 동적 정렬을 지원하세요 — 예를 들어 메서드 이름에서
OrderBy를 제거하고 findAllByStatusAndYear(status, year, sort: Sort) 또는
findAllByStatusAndYear(status, year, pageable: Pageable) 형태로 바꿔 컨트롤러에서 쿼리
파라미터(sort_by, direction)를 받아 적절한 Sort 객체를 전달하도록 구현하면 됩니다.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/VolunteerDetailPersistenceAdapter.kt`:
- Around line 32-105: findDetailByIdOrThrow currently issues seven separate
repository calls (volunteerRepository, volunteerScheduleRepository,
studentParticipationRepository, activityRepository, userActivityRepository,
activityRecurrenceWeekRepository, activityRecurrenceMonthRepository), which will
hurt performance under load; refactor by consolidating these into fewer queries
— e.g., add a custom query or repository method (on VolunteerRepository or a new
VolunteerDetailQueryRepository) that fetches the Volunteer and its related
collections via JOIN FETCH/entity-graph or returns a projection/DTO mapping to
VolunteerDetail, or use batched IN queries and a mapper to assemble
VolunteerDetail in one pass; keep the public API (findDetailByIdOrThrow) but
replace the multiple repository calls with the new consolidated query method and
adapt mapping of Volunteer, schedules, activities, userActivities, weekdays,
monthDate and studentParticipations accordingly.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/VolunteerPersistenceAdapter.kt`:
- Around line 59-69: The parameter type uses the fully-qualified name for
VolunteerStatus in VolunteerPersistenceAdapter.findAll; replace the FQN with an
import by adding "import
finda.findavolunteer.domain.volunteer.enum.VolunteerStatus" at the top and
change the method signature to use VolunteerStatus directly (affecting the
signature of findAll and any references like
volunteerRepository.findAllByStatusAndYearOrderByWorkStartDateDesc and
volunteerMapper::toDomain).

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/activity/CreateUserActivityService.kt`:
- Around line 21-25: UserActivity 생성 시 activity!!.id 대신 it.activityId를 직접 사용하도록
변경하세요: UserActivity(activityId = it.activityId, userId = it.userId). Activity
엔티티 조회는 여전히 존재 검증용으로 유지하되, 생성자에는 조회된 activity의 id를 사용하지 않아 널 강제(!!)를 제거하고
it.activityId로 대체해 안전하게 처리하세요 (참조: CreateUserActivityService, UserActivity,
activity, it.activityId).

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerDetailService.kt`:
- Around line 48-68: The code in VolunteerDetailService calls
userQueryPort.getUserName for each volunteerDetail.studentParticipations and
volunteerDetail.userActivities causing potential N+1 gRPC calls; add a batch
lookup on UserQueryPort (e.g., getUserNames(userIds: List<UUID>): Map<UUID,
String>), collect distinct userIds from volunteerDetail.studentParticipations
and volunteerDetail.userActivities, call the new batch method once to get a
userNameMap, and replace per-item getUserName calls with lookups like
userNameMap[it.userId] ?: throw UserNotFoundException to populate
StudentParticipationResponse and UserActivityResponse.
- Around line 20-70: Add a read-only transaction to the volunteer detail
retrieval: annotate the VolunteerDetailService.execute method (or the
VolunteerDetailService class) with `@Transactional`(readOnly = true) and import
org.springframework.transaction.annotation.Transactional so JPA dirty checking
is disabled for this read operation; ensure the annotation is placed on the
overridden execute(volunteerId: UUID) function (or class) that calls
volunteerDetailQueryPort.findDetailByIdOrThrow and userQueryPort.getUserName.

In
`@finda-volunteer/src/main/resources/db/changelog-volunteer/schema/001-create-volunteer-tables.sql`:
- Around line 74-75: The foreign key constraint name contains a typo: rename
fk_student_particitation_volunteer to fk_student_participation_volunteer to
match the corrected index name idx_student_participation_volunteer_id; update
all occurrences of fk_student_particitation_volunteer (including the second
occurrence noted around the other block) so the CONSTRAINT declaration (FOREIGN
KEY (volunteer_id) REFERENCES tbl_volunteer (id) ON DELETE CASCADE) uses the
corrected constraint name consistently.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a7053def-c62c-4e46-aece-f30d62ac0775

📥 Commits

Reviewing files that changed from the base of the PR and between 52cdef5 and e74e054.

📒 Files selected for processing (48)
  • finda-volunteer/build.gradle.kts
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/.gitkeep
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/ActivityWebAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/dto/request/CreateUserActivityRequest.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/VolunteerWebAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/dto/response/VolunteerDetailResponse.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/dto/response/VolunteerListResponse.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/grpc/UserGrpcAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/ActivityPersistenceAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/UserActivityAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/entity/UserActivityJpaEntity.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/mapper/UserActivityMapper.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/repository/ActivityRepository.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/activity/repository/UserActivityRepository.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/participation/repository/StudentParticipationRepository.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/VolunteerDetailPersistenceAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/VolunteerPersistenceAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/repository/ActivityRecurrenceMonthRepository.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/repository/ActivityRecurrenceWeekRepository.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/repository/VolunteerRepository.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/volunteer/repository/VolunteerScheduleRepository.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/exception/InvalidVolunteerSortByException.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/exception/UserNotFoundException.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/.gitkeep
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/activity/CreateUserActivityUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerDetailUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerListUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/out/.gitkeep
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/out/activity/UserActivityCommandPort.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/out/activity/UserActivityQueryPort.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/out/user/UserQueryPort.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/out/volunteer/VolunteerDetailQueryPort.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/out/volunteer/VolunteerListQueryPort.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/.gitkeep
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/activity/CreateUserActivityService.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerDetailService.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerListService.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/domain/.gitkeep
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/domain/activity/model/UserActivity.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/domain/volunteer/enum/VolunteerSortBy.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/domain/volunteer/model/VolunteerDetail.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/global/error/exception/ErrorCode.kt
  • finda-volunteer/src/main/proto/auth.proto
  • finda-volunteer/src/main/resources/application.yml
  • finda-volunteer/src/main/resources/db/changelog-volunteer/master.xml
  • finda-volunteer/src/main/resources/db/changelog-volunteer/schema/001-create-volunteer-tables.sql
  • finda-volunteer/src/main/resources/db/changelog-volunteer/schema/002-add-user-id-to-user-activity.sql
  • finda-volunteer/src/main/resources/db/changelog-volunteer/schema/003-add-unique-teacher-participation-user-id.sql

Comment on lines +3 to +7
import finda.findavolunteer.adapter.`in`.volunteer.dto.response.VolunteerDetailResponse
import java.util.UUID

interface VolunteerDetailUseCase {
fun execute(volunteerId: UUID): VolunteerDetailResponse
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

애플리케이션 포트가 adapter.in DTO에 의존하고 있습니다.

계층 의존 방향이 뒤집혀 어댑터 변경이 유스케이스 계약에 전파될 수 있습니다. 응답 DTO를 application 계층(예: application.port.in.volunteer.dto)으로 이동해 경계를 정리하는 것을 권장합니다.

♻️ 의존 방향 정리 예시
-import finda.findavolunteer.adapter.`in`.volunteer.dto.response.VolunteerDetailResponse
+import finda.findavolunteer.application.port.`in`.volunteer.dto.response.VolunteerDetailResponse
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerDetailUseCase.kt`
around lines 3 - 7, The VolunteerDetailUseCase interface currently depends on an
adapter-layer DTO (VolunteerDetailResponse) which inverts layer dependencies;
move the DTO from adapter.`in`.volunteer.dto.response to the application layer
(e.g., application.port.in.volunteer.dto) and update the import in
VolunteerDetailUseCase to reference the new package, then update the adapter
implementation(s) and any mappers to construct/convert to the new
application-layer VolunteerDetailResponse so only the adapter depends on the
application contract (keep the interface signature fun execute(volunteerId:
UUID): VolunteerDetailResponse unchanged).

Comment on lines +17 to +27
override fun execute(request: CreateUserActivityRequest) {
request.userActivityList.forEach {
val activity = activityQueryPort.findById(it.activityId)
userActivityCommandPort.save(
UserActivity(
activityId = activity!!.id,
userId = it.userId
)
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Activity 조회 실패 시 NPE 대신 명확한 예외 처리 필요

activity!!.id에서 activityQueryPort.findById가 null을 반환하면 KotlinNullPointerException이 발생합니다. 이는 클라이언트에게 명확한 오류 메시지를 제공하지 못합니다.

프로젝트 컨벤션에 따라 findByIdOrThrow 메서드를 사용하거나, 명시적인 null 체크와 함께 커스텀 예외를 던지는 것이 좋습니다.

🐛 제안된 수정
     `@Transactional`
     override fun execute(request: CreateUserActivityRequest) {
         request.userActivityList.forEach {
-            val activity = activityQueryPort.findById(it.activityId)
+            val activity = activityQueryPort.findById(it.activityId)
+                ?: throw ActivityNotFoundException
             userActivityCommandPort.save(
                 UserActivity(
-                    activityId = activity!!.id,
+                    activityId = activity.id,
                     userId = it.userId
                 )
             )
         }
     }

Based on learnings, exception-throwing "not found" behavior must be implemented in separate methods suffixed with OrThrow, or handled explicitly with a meaningful exception.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/activity/CreateUserActivityService.kt`
around lines 17 - 27, The loop in CreateUserActivityService.execute uses
activityQueryPort.findById and then activity!!.id which causes a
KotlinNullPointerException when not found; replace this with a null-safe lookup
that throws a clear "not found" error per convention: call
activityQueryPort.findByIdOrThrow(it.activityId) if that method exists, or
perform an explicit null check on the result of
activityQueryPort.findById(it.activityId) and throw a domain NotFoundException
(or similar) with a descriptive message before constructing UserActivity and
calling userActivityCommandPort.save(UserActivity(...)).

@@ -0,0 +1,42 @@
syntax = "proto3";

package auth;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

패키지-디렉터리 불일치로 Buf lint가 실패합니다.

Line 3의 package auth; 선언이 현재 파일 위치(finda-volunteer/src/main/proto/auth.proto)와 맞지 않아 PACKAGE_DIRECTORY_MATCH 오류가 발생합니다. 파일을 finda-volunteer/src/main/proto/auth/auth.proto로 이동하거나, 패키지명을 실제 디렉터리 구조와 일치시키는 수정이 필요합니다.

🧰 Tools
🪛 Buf (1.66.1)

[error] 3-3: Files with package "auth" must be within a directory "auth" relative to root but were in directory "finda-volunteer/src/main/proto".

(PACKAGE_DIRECTORY_MATCH)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@finda-volunteer/src/main/proto/auth.proto` at line 3, The package declaration
"package auth;" in auth.proto doesn't match the file's directory layout and
triggers Buf's PACKAGE_DIRECTORY_MATCH error; fix it by either moving auth.proto
into a directory named auth so the path matches the package, or by updating the
package declaration to the correct package name that reflects the current
directory structure (replace "package auth;" accordingly) and update any
import/option references that depend on that package.

Comment on lines +7 to +11
grpc:
client:
auth-service:
address: ${AUTH_GRPC_ADDRESS}
negotiation-type: plaintext
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "application*.yml" -o -name "application*.yaml" | head -20

Repository: team-FINDA/Finda-Backend

Length of output: 883


🏁 Script executed:

cat -n finda-volunteer/src/main/resources/application.yml

Repository: team-FINDA/Finda-Backend

Length of output: 1231


🏁 Script executed:

ls -la finda-volunteer/src/main/resources/ | grep application

Repository: team-FINDA/Finda-Backend

Length of output: 268


🏁 Script executed:

cat -n finda-volunteer/src/main/resources/application-dev.yml

Repository: team-FINDA/Finda-Backend

Length of output: 541


🏁 Script executed:

cat -n finda-volunteer/src/main/resources/application-prod.yml

Repository: team-FINDA/Finda-Backend

Length of output: 543


🏁 Script executed:

rg "AUTH_GRPC_NEGOTIATION_TYPE" --type yaml --type yml

Repository: team-FINDA/Finda-Backend

Length of output: 95


🏁 Script executed:

rg "negotiation-type" finda-volunteer/src/main/resources/

Repository: team-FINDA/Finda-Backend

Length of output: 152


plaintext gRPC 설정이 운영 환경에도 적용됩니다.

기본 application.ymlnegotiation-type: plaintextapplication-prod.yml에서 오버라이드되지 않으므로, 운영 환경에서도 TLS 암호화 없이 gRPC 통신이 진행됩니다. 마이크로서비스 간 민감한 인증 통신이므로 운영 환경에서는 TLS/mTLS를 강제해야 합니다.

기본값을 TLS로 변경하거나, 운영 프로필에서 명시적으로 TLS를 설정하세요. 또는 환경 변수로 제어 가능하도록 수정하세요.

💡 권장 수정 방법
 grpc:
   client:
     auth-service:
       address: ${AUTH_GRPC_ADDRESS}
-      negotiation-type: plaintext
+      negotiation-type: ${AUTH_GRPC_NEGOTIATION_TYPE:TLS}
# application-prod.yml
grpc:
  client:
    auth-service:
      negotiation-type: TLS
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
grpc:
client:
auth-service:
address: ${AUTH_GRPC_ADDRESS}
negotiation-type: plaintext
grpc:
client:
auth-service:
address: ${AUTH_GRPC_ADDRESS}
negotiation-type: ${AUTH_GRPC_NEGOTIATION_TYPE:TLS}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@finda-volunteer/src/main/resources/application.yml` around lines 7 - 11, The
default gRPC client negotiation-type is set to plaintext for auth-service in
application.yml, which enables non-TLS traffic in production; update the
configuration so production enforces TLS by either changing the default
negotiation-type under grpc.client.auth-service to TLS or make negotiation-type
configurable via an environment variable (e.g. AUTH_GRPC_NEGOTIATION_TYPE) and
ensure application-prod.yml explicitly sets
grpc.client.auth-service.negotiation-type: TLS to force TLS/mTLS in production;
locate the negotiation-type setting in application.yml and add/adjust the
corresponding override in application-prod.yml or read from the env var.

Comment on lines +4 to +5
ALTER TABLE tbl_user_activity
ADD COLUMN user_id BINARY(16) NOT NULL;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

기존 데이터가 있으면 마이그레이션이 실패할 수 있습니다.

NOT NULL 컬럼을 바로 추가하면 기존 tbl_user_activity 데이터 때문에 DDL이 실패할 수 있습니다. NULL 허용으로 추가 → 백필 → NOT NULL 전환의 2단계(또는 3단계)로 분리하는 것이 안전합니다.

💡 안전한 마이그레이션 예시
 ALTER TABLE tbl_user_activity
-    ADD COLUMN user_id BINARY(16) NOT NULL;
+    ADD COLUMN user_id BINARY(16) NULL;
+
+-- backfill 수행 (서비스 정책에 맞는 user_id 채움)
+-- UPDATE tbl_user_activity SET user_id = ... WHERE user_id IS NULL;
+
+ALTER TABLE tbl_user_activity
+    MODIFY COLUMN user_id BINARY(16) NOT NULL;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/resources/db/changelog-volunteer/schema/002-add-user-id-to-user-activity.sql`
around lines 4 - 5, The migration should not add user_id as NOT NULL in one
step; instead modify the changelog to add the column as nullable first on
tbl_user_activity (ADD COLUMN user_id BINARY(16) NULL), perform a separate
backfill step that updates tbl_user_activity.user_id with correct values (or a
default/mapped value) in a follow-up changeSet, and finally add a third
changeSet that alters the column to NOT NULL (ALTER TABLE tbl_user_activity
MODIFY COLUMN user_id BINARY(16) NOT NULL) after verifying no NULLs remain;
reference the table name tbl_user_activity and column user_id when implementing
these staged changeSets.

Comment on lines +4 to +5
ALTER TABLE tbl_teacher_participation
ADD CONSTRAINT uk_teacher_participation_user_id UNIQUE (user_id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's explore the changelog directory structure
find . -type f -name "*.sql" | head -20

Repository: team-FINDA/Finda-Backend

Length of output: 904


🏁 Script executed:

#!/bin/bash
# Get the full path to the schema directory mentioned in the file
git ls-files | grep -i "changelog-volunteer" | head -20

Repository: team-FINDA/Finda-Backend

Length of output: 595


🏁 Script executed:

#!/bin/bash
# Look at the specific migration file and surrounding context
git ls-files | grep -i "003-add-unique"

Repository: team-FINDA/Finda-Backend

Length of output: 181


🏁 Script executed:

#!/bin/bash
# Search for other migration files related to teacher_participation
find . -type f -name "*.sql" -exec grep -l "teacher_participation" {} \;

Repository: team-FINDA/Finda-Backend

Length of output: 282


🏁 Script executed:

#!/bin/bash
# Read the table creation migration file to understand the schema
cat ./finda-volunteer/src/main/resources/db/changelog-volunteer/schema/001-create-volunteer-tables.sql

Repository: team-FINDA/Finda-Backend

Length of output: 6027


🏁 Script executed:

#!/bin/bash
# Read the constraint addition migration file
cat ./finda-volunteer/src/main/resources/db/changelog-volunteer/schema/003-add-unique-teacher-participation-user-id.sql

Repository: team-FINDA/Finda-Backend

Length of output: 270


🏁 Script executed:

#!/bin/bash
# Search for Java code that references teacher_participation
find . -type f \( -name "*.java" -o -name "*.kt" \) -exec grep -l "teacher_participation\|TeacherParticipation" {} \;

Repository: team-FINDA/Finda-Backend

Length of output: 1293


🏁 Script executed:

#!/bin/bash
# Check the TeacherParticipationJpaEntity to understand the business logic
cat ./finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/participation/entity/TeacherParticipationJpaEntity.kt

Repository: team-FINDA/Finda-Backend

Length of output: 839


🏁 Script executed:

#!/antml:bash
# Check the repository to see query patterns
cat ./finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/participation/repository/TeacherParticipationRepository.kt

Repository: team-FINDA/Finda-Backend

Length of output: 565


🏁 Script executed:

#!/bin/bash
# Check the domain model
cat ./finda-volunteer/src/main/kotlin/finda/findavolunteer/domain/participation/model/TeacherParticipation.kt

Repository: team-FINDA/Finda-Backend

Length of output: 261


🏁 Script executed:

#!/bin/bash
# Check how teacher participation is used in service
cat ./finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/CreateVolunteerService.kt

Repository: team-FINDA/Finda-Backend

Length of output: 5009


🏁 Script executed:

#!/bin/bash
# Check if there are any other migrations related to teacher_participation
find . -type f -name "*.sql" -o -name "*.xml" | xargs grep -l "teacher_participation" 2>/dev/null | head -20

Repository: team-FINDA/Finda-Backend

Length of output: 282


🏁 Script executed:

#!/bin/bash
# Check if there are other uses of TeacherParticipationQueryPort
find . -type f -name "*.kt" -exec grep -l "TeacherParticipationQueryPort\|TeacherParticipationCommandPort" {} \;

Repository: team-FINDA/Finda-Backend

Length of output: 550


🏁 Script executed:

#!/bin/bash
# Look at the query port to see what operations are expected
cat ./finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/out/participation/TeacherParticipationQueryPort.kt

Repository: team-FINDA/Finda-Backend

Length of output: 394


🏁 Script executed:

#!/bin/bash
# Check the adapter implementation to see how it uses the query port
cat ./finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/persistence/participation/TeacherParticipationAdapter.kt

Repository: team-FINDA/Finda-Backend

Length of output: 1698


🏁 Script executed:

#!/bin/bash
# Check command port to understand save behavior
cat ./finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/out/participation/TeacherParticipationCommandPort.kt

Repository: team-FINDA/Finda-Backend

Length of output: 334


UNIQUE(user_id) 제약이 레파지토리 설계와 충돌합니다.

TeacherParticipationRepository.findAllByUserId(userId)List<TeacherParticipationJpaEntity>를 반환하는 것은 사용자당 여러 레코드를 처리할 수 있도록 설계되었음을 의미합니다. 그러나 UNIQUE(user_id) 제약은 사용자당 최대 1개 레코드만 허용하므로, 같은 교사가 여러 봉사활동에 참여할 수 없습니다. tbl_student_participation과 달리 이 제약이 있는 이유가 명확하지 않으므로, (user_id, volunteer_id) 복합 유니크 키로 변경하거나 제약을 제거해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/resources/db/changelog-volunteer/schema/003-add-unique-teacher-participation-user-id.sql`
around lines 4 - 5, The migration adds a UNIQUE(user_id) constraint
(uk_teacher_participation_user_id) on tbl_teacher_participation which conflicts
with the repository design (TeacherParticipationRepository.findAllByUserId
returning List<TeacherParticipationJpaEntity>) and prevents a teacher from
having multiple participation records; update the migration to either drop this
UNIQUE constraint or replace it with a composite unique on (user_id,
volunteer_id) to match tbl_student_participation semantics and allow multiple
participations per user, ensuring the constraint name
(uk_teacher_participation_user_id) and table (tbl_teacher_participation) are
adjusted accordingly.

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.

♻️ Duplicate comments (1)
finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/ActivityWebAdapter.kt (1)

16-21: ⚠️ Potential issue | 🟡 Minor

생성 API는 201 상태 코드를 명시해 주세요.

Line [16]~Line [21]은 리소스 생성 동작인데 현재 기본 200 응답이 내려갈 수 있습니다. @ResponseStatus(HttpStatus.CREATED) 또는 ResponseEntity.created(...)로 계약을 명확히 하는 게 좋습니다.

제안 diff
 import jakarta.validation.Valid
+import org.springframework.http.HttpStatus
 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.ResponseStatus
 import org.springframework.web.bind.annotation.RestController
@@
-    `@PostMapping`("/user")
+    `@PostMapping`("/user")
+    `@ResponseStatus`(HttpStatus.CREATED)
     fun createUserActivity(
         `@Valid` `@RequestBody`
         request: CreateUserActivityRequest
     ) =
         createUserActivityUseCase.execute(request)
#!/bin/bash
# 생성 엔드포인트의 상태코드 명시 여부 점검
rg -n -C2 '@PostMapping|@ResponseStatus|ResponseEntity' finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/ActivityWebAdapter.kt
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/ActivityWebAdapter.kt`
around lines 16 - 21, The createUserActivity handler in ActivityWebAdapter
currently returns the use-case result without specifying a 201 status; update
the controller to explicitly return HTTP 201 for resource creation by either
adding `@ResponseStatus`(HttpStatus.CREATED) to the createUserActivity function or
changing it to return a ResponseEntity (e.g.,
ResponseEntity.created(...).body(...)) that wraps
createUserActivityUseCase.execute(request); ensure the chosen change is applied
to the createUserActivity method signature so callers receive a CREATED
response.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/ActivityWebAdapter.kt`:
- Around line 16-21: The createUserActivity handler in ActivityWebAdapter
currently returns the use-case result without specifying a 201 status; update
the controller to explicitly return HTTP 201 for resource creation by either
adding `@ResponseStatus`(HttpStatus.CREATED) to the createUserActivity function or
changing it to return a ResponseEntity (e.g.,
ResponseEntity.created(...).body(...)) that wraps
createUserActivityUseCase.execute(request); ensure the chosen change is applied
to the createUserActivity method signature so callers receive a CREATED
response.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7e1249d2-301e-4aee-bef3-387012561c93

📥 Commits

Reviewing files that changed from the base of the PR and between 28b7a7c and e2d4cd7.

📒 Files selected for processing (5)
  • finda-volunteer/build.gradle.kts
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/ActivityWebAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/dto/request/CreateUserActivityRequest.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/VolunteerWebAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/grpc/UserGrpcAdapter.kt
✅ Files skipped from review due to trivial changes (1)
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/dto/request/CreateUserActivityRequest.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • finda-volunteer/build.gradle.kts
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/out/grpc/UserGrpcAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/VolunteerWebAdapter.kt

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/CreateVolunteerService.kt (1)

117-119: TODO 항목 추적을 위한 이슈 생성을 권장합니다.

Outbox 패턴과 Debezium 관련 TODO가 3개 있습니다. 이러한 작업들이 추적되고 있는지 확인해 주세요.

이 TODO 항목들을 추적하기 위한 GitHub 이슈를 생성해 드릴까요?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/CreateVolunteerService.kt`
around lines 117 - 119, The three TODO comments in CreateVolunteerService (the
outbox-related TODOs and the Debezium TODO) need tracked GitHub issues: create
separate issues for "outbox: send activity hours & recruitment period", "outbox:
send activity days & remind time", and "Debezium: read multiple tables to
publish events", then update each TODO in CreateVolunteerService.kt to reference
the new issue ID or URL (e.g., TODO(issue#123) or TODO: see
https://.../issues/123) and add brief acceptance criteria and owner in the
issue; ensure the issues include required work (design, implementation, tests,
and migration/backfill if needed) and assign/milestone labels so they are
discoverable.
finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/mapper/CreateVolunteerRequestMapper.kt (1)

9-9: CreateVolunteerRequest의 personnal 필드명을 personnel로 수정하는 것을 권장합니다.

personnalpersonnel의 오타이며, 매퍼에서 올바르게 변환하고 있습니다. 다만 API 호출자와의 계약이 정해져 있다면 breaking change가 될 수 있으므로, API 버전 관리 계획을 고려하여 수정하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/mapper/CreateVolunteerRequestMapper.kt`
at line 9, The CreateVolunteerRequest data class has a typo: the field is named
`personnal` but should be `personnel`; update the field name in
CreateVolunteerRequest to `personnel`, then update the mapper in
CreateVolunteerRequestMapper (where `personnel = personnal`) to use the
corrected name, and search/replace other references/usages of `personnal`
(constructors, JSON serialization names, tests, API docs) so everything
compiles; if this is a public API, add migration/versioning notes or keep a
backward-compatible JSON alias (e.g., `@JsonProperty`("personnal") -> personnel)
to avoid breaking clients.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerDetailService.kt`:
- Around line 48-66: The code calls userQueryPort.getUserName repeatedly for the
same userId while building StudentParticipationResult and UserActivityResult;
instead, collect all distinct userIds from volunteerDetail.studentParticipations
and volunteerDetail.userActivities, call userQueryPort.getUserName once per
distinct id to build a Map<userId, userName>, then replace direct calls in the
StudentParticipationResult and UserActivityResult constructors to lookup the
name from that map and throw UserNotFoundException if a required id is missing;
update references to volunteerDetail.studentParticipations,
volunteerDetail.userActivities, StudentParticipationResult, UserActivityResult,
and userQueryPort.getUserName accordingly.

---

Nitpick comments:
In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/mapper/CreateVolunteerRequestMapper.kt`:
- Line 9: The CreateVolunteerRequest data class has a typo: the field is named
`personnal` but should be `personnel`; update the field name in
CreateVolunteerRequest to `personnel`, then update the mapper in
CreateVolunteerRequestMapper (where `personnel = personnal`) to use the
corrected name, and search/replace other references/usages of `personnal`
(constructors, JSON serialization names, tests, API docs) so everything
compiles; if this is a public API, add migration/versioning notes or keep a
backward-compatible JSON alias (e.g., `@JsonProperty`("personnal") -> personnel)
to avoid breaking clients.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/CreateVolunteerService.kt`:
- Around line 117-119: The three TODO comments in CreateVolunteerService (the
outbox-related TODOs and the Debezium TODO) need tracked GitHub issues: create
separate issues for "outbox: send activity hours & recruitment period", "outbox:
send activity days & remind time", and "Debezium: read multiple tables to
publish events", then update each TODO in CreateVolunteerService.kt to reference
the new issue ID or URL (e.g., TODO(issue#123) or TODO: see
https://.../issues/123) and add brief acceptance criteria and owner in the
issue; ensure the issues include required work (design, implementation, tests,
and migration/backfill if needed) and assign/milestone labels so they are
discoverable.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d28a508-b5f9-49d9-bdb2-7ff6533b85c2

📥 Commits

Reviewing files that changed from the base of the PR and between e2d4cd7 and c276ebf.

📒 Files selected for processing (17)
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/ActivityWebAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/mapper/CreateUserActivityRequestMapper.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/VolunteerWebAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/mapper/CreateVolunteerRequestMapper.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/mapper/VolunteerResultMapper.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/activity/CreateUserActivityUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/activity/dto/request/CreateUserActivityCommand.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/CreateVolunteerUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerDetailUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerListUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/dto/request/CreateVolunteerCommand.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/dto/response/VolunteerDetailResult.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/dto/response/VolunteerListResult.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/activity/CreateUserActivityService.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/CreateVolunteerService.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerDetailService.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerListService.kt
✅ Files skipped from review due to trivial changes (6)
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerDetailUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/dto/response/VolunteerListResult.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/activity/CreateUserActivityUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/VolunteerListUseCase.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/activity/dto/request/CreateUserActivityCommand.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/port/in/volunteer/dto/request/CreateVolunteerCommand.kt
🚧 Files skipped from review as they are similar to previous changes (4)
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/activity/ActivityWebAdapter.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerListService.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/activity/CreateUserActivityService.kt
  • finda-volunteer/src/main/kotlin/finda/findavolunteer/adapter/in/volunteer/VolunteerWebAdapter.kt

Comment on lines +48 to +66
studentParticipationResultList = volunteerDetail.studentParticipations.map {
StudentParticipationResult(
userId = it.userId,
name = userQueryPort.getUserName(it.userId) ?: throw UserNotFoundException,
status = it.status,
participatedAt = it.participatedAt
)
},
activityResultList = volunteerDetail.activities.map {
ActivityResult(
activityId = it.id,
activityName = it.activityName
)
},
userActivityResultList = volunteerDetail.userActivities.map {
UserActivityResult(
userId = it.userId,
userName = userQueryPort.getUserName(it.userId) ?: throw UserNotFoundException,
activityId = it.activityId
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

동일 사용자명 조회의 중복 외부 호출을 줄여야 합니다.

Line 48-66에서 userQueryPort.getUserName을 항목별로 직접 호출하면, 동일 userId에 대해 gRPC 호출이 반복되어 응답 지연과 실패 가능성이 커집니다. 사용자 ID를 먼저 dedupe 후 한 번씩만 조회해 재사용하는 방식이 안전합니다.

♻️ 제안 수정안
@@
     override fun execute(volunteerId: UUID): VolunteerDetailResult {
         val volunteerDetail = volunteerDetailQueryPort.findDetailByIdOrThrow(volunteerId)
         val volunteer = volunteerDetail.volunteer
+        val userNameById = (
+            volunteerDetail.studentParticipations.map { it.userId } +
+                volunteerDetail.userActivities.map { it.userId }
+            )
+            .distinct()
+            .associateWith { userId ->
+                userQueryPort.getUserName(userId) ?: throw UserNotFoundException
+            }
 
         return VolunteerDetailResult(
@@
             studentParticipationResultList = volunteerDetail.studentParticipations.map {
                 StudentParticipationResult(
                     userId = it.userId,
-                    name = userQueryPort.getUserName(it.userId) ?: throw UserNotFoundException,
+                    name = userNameById.getValue(it.userId),
                     status = it.status,
                     participatedAt = it.participatedAt
                 )
             },
@@
             userActivityResultList = volunteerDetail.userActivities.map {
                 UserActivityResult(
                     userId = it.userId,
-                    userName = userQueryPort.getUserName(it.userId) ?: throw UserNotFoundException,
+                    userName = userNameById.getValue(it.userId),
                     activityId = it.activityId
                 )
             }
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
studentParticipationResultList = volunteerDetail.studentParticipations.map {
StudentParticipationResult(
userId = it.userId,
name = userQueryPort.getUserName(it.userId) ?: throw UserNotFoundException,
status = it.status,
participatedAt = it.participatedAt
)
},
activityResultList = volunteerDetail.activities.map {
ActivityResult(
activityId = it.id,
activityName = it.activityName
)
},
userActivityResultList = volunteerDetail.userActivities.map {
UserActivityResult(
userId = it.userId,
userName = userQueryPort.getUserName(it.userId) ?: throw UserNotFoundException,
activityId = it.activityId
val userNameById = (
volunteerDetail.studentParticipations.map { it.userId } +
volunteerDetail.userActivities.map { it.userId }
)
.distinct()
.associateWith { userId ->
userQueryPort.getUserName(userId) ?: throw UserNotFoundException
}
return VolunteerDetailResult(
studentParticipationResultList = volunteerDetail.studentParticipations.map {
StudentParticipationResult(
userId = it.userId,
name = userNameById.getValue(it.userId),
status = it.status,
participatedAt = it.participatedAt
)
},
activityResultList = volunteerDetail.activities.map {
ActivityResult(
activityId = it.id,
activityName = it.activityName
)
},
userActivityResultList = volunteerDetail.userActivities.map {
UserActivityResult(
userId = it.userId,
userName = userNameById.getValue(it.userId),
activityId = it.activityId
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@finda-volunteer/src/main/kotlin/finda/findavolunteer/application/service/volunteer/VolunteerDetailService.kt`
around lines 48 - 66, The code calls userQueryPort.getUserName repeatedly for
the same userId while building StudentParticipationResult and
UserActivityResult; instead, collect all distinct userIds from
volunteerDetail.studentParticipations and volunteerDetail.userActivities, call
userQueryPort.getUserName once per distinct id to build a Map<userId, userName>,
then replace direct calls in the StudentParticipationResult and
UserActivityResult constructors to lookup the name from that map and throw
UserNotFoundException if a required id is missing; update references to
volunteerDetail.studentParticipations, volunteerDetail.userActivities,
StudentParticipationResult, UserActivityResult, and userQueryPort.getUserName
accordingly.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🌟 봉사 조회 및 역할 지정 api 추가

2 participants