Skip to content

refactor: Google Sheet API 전화번호 포맷팅 및 마이그레이션 버그 수정#440

Merged
JanooGwan merged 6 commits intodevelopfrom
refactor/google-sheet-api-v4
Mar 24, 2026
Merged

refactor: Google Sheet API 전화번호 포맷팅 및 마이그레이션 버그 수정#440
JanooGwan merged 6 commits intodevelopfrom
refactor/google-sheet-api-v4

Conversation

@JanooGwan
Copy link
Contributor

@JanooGwan JanooGwan commented Mar 24, 2026

🔍 개요

Google Sheet API 테스트 결과 발견된 두 가지 버그를 수정합니다.

🚀 주요 변경 내용

1. 전화번호 포맷팅 통일 (010-XXXX-XXXX)

  • PhoneNumberNormalizer.format() 메서드 추가
    • 구글 시트에서 앞자리 0이 잘린 경우(1012345678) 자동 복구
    • 010-XXXX-XXXX 형식으로 통일 출력
  • SheetMigrationService.buildTargetRow(): normalize()format() 교체
  • SheetSyncExecutor.buildColumnData(): "'" + phone 방식 → format() 교체
  • SheetSyncExecutor.clearAndWriteAll(): 동일하게 format() 적용

2. 마이그레이션 시 파일이 루트에 생성되는 버그 수정

  • SheetMigrationService.copyTemplate()files().copy() 응답에서 getParents()null로 반환되는 경우, 별도 files().get() 호출로 parents를 재조회하여 removeParents 누락 방지

💬 참고 사항

  • Google Drive API v3에서 files().copy() 응답의 parents 필드는 간헐적으로 null로 반환됨
  • SheetSyncExecutor의 동기화(syncMembersToSheet)는 서비스 계정으로 직접 쓰기이므로 Drive OAuth 연동 여부와 무관하게 동작함
  • SheetMigrationService에서는 Drive OAuth 미연동 시 NOT_FOUND_GOOGLE_DRIVE_AUTH 예외를 던져 정상 처리됨

✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

@JanooGwan JanooGwan self-assigned this Mar 24, 2026
@JanooGwan JanooGwan added the 리팩토링 리팩터링을 위한 이슈입니다. label Mar 24, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0e17a62e-41f9-4361-93c4-5f19adf6409b

📥 Commits

Reviewing files that changed from the base of the PR and between 797aa03 and 1465267.

📒 Files selected for processing (1)
  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
🔇 Additional comments (2)
src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java (2)

277-336: LGTM - copyTemplate 방어 로직 및 고아 파일 정리 구현이 적절합니다.

files().copy() 응답의 parentsnull로 반환되는 엣지 케이스를 재조회로 처리하고, 실패 시 생성된 파일을 정리하는 흐름이 명확합니다. deleteFile()이 best-effort로 예외를 흡수하는 것도 정리 코드로서 적절한 설계입니다.


440-442: LGTM - 전화번호 포맷팅 변경이 PR 목표에 부합합니다.

format() 메서드가 구글 시트에서 앞자리 0이 잘린 경우를 복구하고 010-XXXX-XXXX 형식으로 포맷팅하는 동작이 PR 요구사항과 일치합니다. cellValue != null 체크로 NPE도 방지됩니다.


📝 Walkthrough

Walkthrough

Drive 복사 후 부모(folder) 메타데이터가 비어있을 때 추가로 부모를 재조회하고, 재조회 실패 또는 부모가 여전히 비어있으면 생성된 파일을 삭제하고 예외로 중단하도록 복사/이동 흐름을 강화했습니다. 전화번호 포맷팅은 PhoneNumberNormalizer.format()으로 대체되고, 시트 쓰기에서 null 값은 빈 문자열로 대체됩니다.

Changes

Cohort / File(s) Summary
Drive 부모 재조회 및 오류 처리
src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
copyTemplate에서 files().copy(...).setFields("id, parents")parents가 null/빈 경우 files().get(newFileId).setFields("parents")로 재조회. 재조회 중 IOException 발생 시 또는 재조회 후에도 부모가 없을 경우 새로 생성된 파일을 삭제하고 CustomException(FAILED_SYNC_GOOGLE_SHEET)을 던져 복사/이동 흐름을 중단하도록 변경. 예외 경로에서도 orphan 파일 삭제를 보장하도록 newFileId 범위를 확장함.
전화번호 포맷팅 유틸 추가
src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java
public static String format(String phone) 추가: 숫자만 추출하고 길이/접두사 규칙에 따라 하이픈을 넣어 포맷함. null 입력은 null, 숫자 없음은 "" 반환. 기존 normalize/looksLikePhoneNumber는 유지.
시트 쓰기 null 처리 및 포맷 적용
src/main/java/gg/agit/konect/domain/club/service/SheetSyncExecutor.java
전화번호 생성/출력에서 format() 사용으로 변경. 매핑 컬럼 작성 시 putValuenull""로 대체하도록 수정하고, 전체 시트 쓰기에서 전화번호 필드에 phone != null ? phone : "" 대체를 적용함.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Service as SheetMigrationService
    participant Drive as Google Drive API
    participant RM as RollbackManager

    Service->>Drive: files().copy(...).setFields("id, parents")
    Drive-->>Service: { id: newFileId, parents: null/empty }
    alt parents empty
        Service->>Drive: files().get(newFileId).setFields("parents")
        Drive-->>Service: { parents: [...] } 
        alt re-fetch success & parents present
            Service->>Drive: files().update(addParents=targetFolder, removeParents=currentParents)
            Drive-->>Service: updated file
        else re-fetch fails (IOException) or parents still empty
            Service->>Service: delete file(`newFileId`)
            Service->>RM: throw CustomException(FAILED_SYNC_GOOGLE_SHEET)
        end
    else parents present
        Service->>Drive: files().update(addParents=targetFolder, removeParents=parents)
        Drive-->>Service: updated file
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

버그

🐰 복사된 파일 길을 쫓아,
부모가 없으면 다시 묻고,
실패면 깔끔히 지우네,
숫자엔 하이픈 춤을 주고,
빈 곳엔 빈 글자를 남기네. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% 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
Title check ✅ Passed PR 제목이 변경 사항의 주요 내용을 명확하게 요약하고 있습니다. 전화번호 포맷팅 리팩토링과 마이그레이션 버그 수정이라는 두 가지 핵심 변경 사항을 간결하게 나타내고 있습니다.
Description check ✅ Passed PR 설명이 변경 사항과 관련성이 높습니다. 전화번호 포맷팅 추가, 마이그레이션 버그 수정, Google Drive API의 부분적 반응에 대한 설명을 포함하고 있습니다.

✏️ 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 refactor/google-sheet-api-v4

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.

Copy link

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java (1)

291-316: ⚠️ Potential issue | 🟠 Major

[LEVEL: medium] 재조회 실패 시 파일이 여러 폴더에 존재할 수 있음

재조회(files().get)도 실패하면 currentParents가 null/empty 상태로 유지되어 removeParents가 빈 문자열이 됩니다. → setRemoveParents("") 호출 시 원래 위치(루트)에서 파일이 제거되지 않아 파일이 두 폴더에 동시에 존재하게 됩니다. → 재조회 실패 시 경고 로그 대신 예외를 던지거나, 최소한 사용자에게 알려주는 방식을 고려하세요.

🛠️ 수정 제안
                 } catch (IOException ex) {
                     log.warn(
                         "Failed to re-fetch parents for copied file. fileId={}, cause={}",
                         newFileId, ex.getMessage()
                     );
+                    // 부모 폴더를 확인할 수 없으면 이동을 건너뛰고 경고
+                    log.warn("Skipping folder move due to unknown current parents. fileId={}", newFileId);
+                    return newFileId;
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java`
around lines 291 - 316, The re-fetch path in SheetMigrationService leaves
currentParents empty on files().get failure so removeParents becomes "" and
setRemoveParents("") causes the file to remain in the original folder
(duplicated); change the catch of IOException from merely logging to rethrowing
(or throw a custom unchecked exception) so the method aborts instead of calling
driveService.files().update with an empty removeParents. Specifically, in the
block that calls driveService.files().get(newFileId) and assigns currentParents,
replace the log.warn(...) in the catch with throwing the caught IOException (or
wrap+throw a RuntimeException) so code after that (which uses removeParents and
calls setRemoveParents) is not executed with invalid state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java`:
- Around line 66-71: Remove the unreachable branch handling 10-digit numbers
starting with "010" in PhoneNumberNormalizer (the if that checks digits.length()
== DIGITS_10 && digits.startsWith("010")), or if you want to keep it for
documentation, replace it with a clear comment explaining why it's unreachable
(010 numbers are always 11 digits) and why the branch is intentionally omitted;
update or delete related constants/paths (DIGITS_10, IDX_3, IDX_6) usage if they
become unused after removal to keep the class clean.
- Around line 54-57: The current recovery logic in PhoneNumberNormalizer that
prepends "0" when digits.length() == DIGITS_10 and digits.startsWith("1") is too
broad and converts service numbers (e.g., 1588-xxxx) incorrectly; narrow the
condition to only recover likely-missing leading-zero mobile prefixes by
checking digits.startsWith("10") (i.e., the 010 mobile prefix without the 0) or
otherwise match a stricter pattern for mobile numbers before setting digits =
"0" + digits; update the check around DIGITS_10 and the digits variable (in the
method handling normalization) accordingly.

---

Outside diff comments:
In `@src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java`:
- Around line 291-316: The re-fetch path in SheetMigrationService leaves
currentParents empty on files().get failure so removeParents becomes "" and
setRemoveParents("") causes the file to remain in the original folder
(duplicated); change the catch of IOException from merely logging to rethrowing
(or throw a custom unchecked exception) so the method aborts instead of calling
driveService.files().update with an empty removeParents. Specifically, in the
block that calls driveService.files().get(newFileId) and assigns currentParents,
replace the log.warn(...) in the catch with throwing the caught IOException (or
wrap+throw a RuntimeException) so code after that (which uses removeParents and
calls setRemoveParents) is not executed with invalid state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1361c910-bb32-47e2-a972-a8de01b6ae69

📥 Commits

Reviewing files that changed from the base of the PR and between a2d71bf and b860eec.

📒 Files selected for processing (3)
  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
  • src/main/java/gg/agit/konect/domain/club/service/SheetSyncExecutor.java
  • src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
  • src/main/java/gg/agit/konect/domain/club/service/SheetSyncExecutor.java
  • src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
  • src/main/java/gg/agit/konect/domain/club/service/SheetSyncExecutor.java
  • src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java
🔇 Additional comments (4)
src/main/java/gg/agit/konect/domain/club/service/SheetSyncExecutor.java (3)

182-183: LGTM!

PhoneNumberNormalizer.format() 호출로 전화번호 포맷팅이 통일되었고, putValue에서 null 처리가 적절히 이루어집니다.


201-201: LGTM!

null 값을 빈 문자열로 변환하여 시트에 null이 기록되는 것을 방지합니다.


217-226: LGTM!

전화번호 포맷팅과 null 처리가 적절합니다.

src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java (1)

429-432: LGTM!

PhoneNumberNormalizer.format() 호출로 전화번호 포맷팅이 통일되었습니다. (단, PhoneNumberNormalizer.format()의 앞자리 0 복구 로직 이슈는 해당 파일 리뷰에서 별도로 지적되었습니다.)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Google Sheets/Drive 연동 기반의 동아리 명단 동기화·마이그레이션에서, 전화번호 표시 형식을 통일하고(0 잘림 복구 포함) 템플릿 복사 후 파일이 루트에 남는 문제를 완화하려는 PR입니다.

Changes:

  • PhoneNumberNormalizer.format() 추가로 전화번호를 하이픈 포함 형태로 포맷팅(일부 케이스 0 잘림 복구)
  • 시트 동기화(SheetSyncExecutor)에서 전화번호 입력 방식을 format() 기반으로 변경 및 null 값 안전 처리
  • 템플릿 복사 후 폴더 이동 시 parents가 null로 오는 케이스를 files().get()로 재조회하도록 보완

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java 전화번호 포맷팅 메서드 추가 및 관련 상수 도입
src/main/java/gg/agit/konect/domain/club/service/SheetSyncExecutor.java 시트 쓰기 시 전화번호 포맷 통일 및 null 값 처리 개선
src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java 템플릿 복사 후 parents 재조회로 폴더 이동 안정성 개선, 마이그레이션 시 전화번호 포맷 적용

Comment on lines +39 to +43
* 전화번호를 010-XXXX-XXXX 형식으로 변환합니다.
* 구글 시트에서 앞자리 0이 잘린 경우(10자리, 1로 시작)도 복구합니다.
* 예) 1012345678 -> 010-1234-5678
* 01012345678 -> 010-1234-5678
* 010-1234-5678 -> 010-1234-5678
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

format()의 Javadoc이 “010-XXXX-XXXX 형식으로 변환”이라고 되어 있지만, 실제 구현은 02 지역번호/기타 10자리/11자리도 처리하고(또는 조건에 안 맞으면 하이픈 없이 digits 그대로 반환) 문서와 동작이 불일치합니다. 문서를 구현에 맞게 수정하거나, 정말로 010-XXXX-XXXX로만 통일하려는 의도라면 그에 맞게 포맷 규칙/리턴값을 조정해 주세요.

Suggested change
* 전화번호를 010-XXXX-XXXX 형식으로 변환합니다.
* 구글 시트에서 앞자리 0 잘린 경우(10자리, 1 시작) 복구합니다.
* ) 1012345678 -> 010-1234-5678
* 01012345678 -> 010-1234-5678
* 010-1234-5678 -> 010-1234-5678
* 전화번호에서 숫자만 추출한 길이와 패턴에 따라 하이픈(-) 삽입하여 포맷팅합니다.
* 구글 시트에서 앞자리 0 잘린 경우(10자리, 1 시작) 복구합니다.
*
* <p>포맷 규칙은 다음과 같습니다.
* <ul>
* <li>구글 시트에서 앞자리 0 잘린 10자리 번호(1 시작): 앞에 0 붙여 11자리로 복구</li>
* <li>11자리: {@code XXX-XXXX-XXXX}</li>
* <li>010으로 시작하는 10자리: {@code 010-XXX-XXXX}</li>
* <li>서울 지역번호 02:
* <ul>
* <li>10자리: {@code 02-XXXX-XXXX}</li>
* <li>9자리: {@code 02-XXX-XXXX}</li>
* </ul>
* </li>
* <li> 10자리: {@code XXX-XXX-XXXX}</li>
* <li> 조건에 모두 해당하지 않는 경우: 하이픈 없이 숫자만 그대로 반환</li>
* </ul>
*
* <p>)
* <pre>
* 1012345678 -> 010-1234-5678 (구글 시트에서 0 잘린 경우 복구)
* 01012345678 -> 010-1234-5678
* 010-1234-5678 -> 010-1234-5678
* 0212345678 -> 02-1234-5678
* 021234567 -> 02-123-4567
* 0311234567 -> 031-123-4567
* 1234567 -> 1234567 (길이가 맞지 않아 하이픈 없이 반환)
* </pre>

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +10
private static final int DIGITS_9 = 9;
private static final int DIGITS_10 = 10;
private static final int DIGITS_11 = 11;
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

PHONE_NUMBER_MIN_DIGITS/MAX_DIGITS(9~11)와 별도로 DIGITS_9/10/11 상수를 추가해 같은 의미의 숫자를 중복 정의하고 있어 향후 변경 시 값 불일치가 생길 수 있습니다. 한쪽만 기준으로 쓰도록 정리(예: DIGITS_* 제거, 또는 MIN/MAX를 DIGITS_*로 대체)하는 편이 안전합니다.

Suggested change
private static final int DIGITS_9 = 9;
private static final int DIGITS_10 = 10;
private static final int DIGITS_11 = 11;
private static final int DIGITS_9 = PHONE_NUMBER_MIN_DIGITS;
private static final int DIGITS_10 = PHONE_NUMBER_MIN_DIGITS + 1;
private static final int DIGITS_11 = PHONE_NUMBER_MAX_DIGITS;

Copilot uses AI. Check for mistakes.
Comment on lines 309 to 311
String removeParents = (currentParents != null && !currentParents.isEmpty())
? String.join(",", currentParents)
: "";
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

parents 재조회 실패(또는 재조회 후에도 parents가 비어 있음)해도 경고 로그만 남기고 removeParents를 빈 문자열로 둔 채 update를 계속 진행하면, 파일이 루트(또는 기존 폴더)에 남아 ‘폴더 이동’이 보장되지 않습니다. 마이그레이션의 핵심 전제(지정 폴더로 이동)가 깨지는 경우이므로, parents 확보에 실패하면 예외로 처리해 롤백되도록 하거나(또는 update 호출 자체를 중단) 실패를 호출자에게 명확히 전달하는 방식이 더 안전합니다.

Suggested change
String removeParents = (currentParents != null && !currentParents.isEmpty())
? String.join(",", currentParents)
: "";
// parents를 끝내 확보하지 못한 경우, 폴더 이동이 보장되지 않으므로 예외로 처리해 롤백되도록 한다.
if (currentParents == null || currentParents.isEmpty()) {
log.error("Failed to determine parents for copied file. fileId={}", newFileId);
throw new IllegalStateException("Cannot determine parents for copied file: " + newFileId);
}
String removeParents = String.join(",", currentParents);

Copilot uses AI. Check for mistakes.
Copy link

@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.

Caution

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

⚠️ Outside diff range comments (1)
src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java (1)

287-314: ⚠️ Potential issue | 🟠 Major

[LEVEL: medium] 예외 경로에서 복제 파일 보상 삭제가 누락됩니다.

Line 287-314에서 files().copy() 이후 parents 재조회가 실패하면 CustomException을 던지지만, 이미 생성된 newFileId를 정리하지 않아 보상 처리가 비어 있습니다.
이 경우(예: copy 응답과 재조회 모두 parents 미확보) API는 실패로 끝나고 Drive에 고아 스프레드시트가 남아 누적될 수 있습니다.
newFileId를 try 바깥 변수로 보관해 CustomException/IOException 경로에서 deleteFile(...)을 호출하거나 복사 직후 롤백 훅을 등록해 정리를 보장하세요; As per coding guidelines "보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다."

최소 수정 예시
 private String copyTemplate(Drive driveService, String templateId, String clubName, String targetFolderId) {
+    String newFileId = null;
     try {
         String title = NEW_SHEET_TITLE_PREFIX + clubName;
         File copyMetadata = new File().setName(title);

         File copied = driveService.files().copy(templateId, copyMetadata)
             .setFields("id, parents")
             .execute();

-        String newFileId = copied.getId();
+        newFileId = copied.getId();

         if (targetFolderId != null) {
             List<String> currentParents = copied.getParents();
             ...
         }

         log.info("Template copied by user. newId={}, folderId={}", newFileId, targetFolderId);
         return newFileId;

+    } catch (CustomException e) {
+        if (newFileId != null) {
+            deleteFile(driveService, newFileId);
+        }
+        throw e;
     } catch (IOException e) {
+        if (newFileId != null) {
+            deleteFile(driveService, newFileId);
+        }
         log.error("Failed to copy template. cause={}", e.getMessage(), e);
         throw CustomException.of(ApiResponseCode.FAILED_SYNC_GOOGLE_SHEET);
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java`
around lines 287 - 314, The copy error path can leave an orphaned Google Sheet
because newFileId (from copied.getId()) is not deleted when re-fetching parents
fails; update SheetMigrationService to ensure cleanup by storing newFileId in a
variable visible to the exception handlers and invoking the existing
deleteFile(...) (or equivalent) on newFileId in both the IOException catch and
the subsequent parents-null check before throwing
CustomException.of(ApiResponseCode.FAILED_SYNC_GOOGLE_SHEET); ensure any
rollback is safe if deleteFile fails (log and proceed to throw) and reference
the copied/newFileId, driveService.files().get(...) parents re-fetch logic, and
CustomException path when implementing the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java`:
- Around line 287-314: The copy error path can leave an orphaned Google Sheet
because newFileId (from copied.getId()) is not deleted when re-fetching parents
fails; update SheetMigrationService to ensure cleanup by storing newFileId in a
variable visible to the exception handlers and invoking the existing
deleteFile(...) (or equivalent) on newFileId in both the IOException catch and
the subsequent parents-null check before throwing
CustomException.of(ApiResponseCode.FAILED_SYNC_GOOGLE_SHEET); ensure any
rollback is safe if deleteFile fails (log and proceed to throw) and reference
the copied/newFileId, driveService.files().get(...) parents re-fetch logic, and
CustomException path when implementing the fix.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c7729d63-579e-45a9-b84f-e5d11e2e2fe3

📥 Commits

Reviewing files that changed from the base of the PR and between b860eec and 797aa03.

📒 Files selected for processing (2)
  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
  • src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java
  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/main/java/gg/agit/konect/global/util/PhoneNumberNormalizer.java
  • src/main/java/gg/agit/konect/domain/club/service/SheetMigrationService.java

@JanooGwan JanooGwan changed the title [리팩토링] Google Sheet API 전화번호 포맷팅 및 마이그레이션 버그 수정 refactor: Google Sheet API 전화번호 포맷팅 및 마이그레이션 버그 수정 Mar 24, 2026
@JanooGwan JanooGwan requested a review from Copilot March 24, 2026 08:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment on lines +60 to +63
if (phone == null) {
return null;
}
String digits = phone.trim().replaceAll("[^0-9]", "");
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

format()에서 숫자 추출/trim/빈문자 처리 로직이 기존 normalize()와 중복됩니다. 한쪽만 수정될 때 동작이 달라질 수 있으니 format() 내부에서 normalize(phone)를 재사용하도록 리팩터링하는 편이 유지보수에 안전합니다.

Suggested change
if (phone == null) {
return null;
}
String digits = phone.trim().replaceAll("[^0-9]", "");
String digits = normalize(phone);
if (digits == null) {
return null;
}

Copilot uses AI. Check for mistakes.
Comment on lines +306 to +307
"Failed to re-fetch parents for copied file. fileId={}, cause={}",
newFileId, ex.getMessage()
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

부모 폴더 재조회 실패 로그에서 ex.getMessage()만 기록되고 예외 객체가 전달되지 않아 스택트레이스가 누락됩니다. 운영 디버깅을 위해 log.error(..., ex) 형태로 예외를 함께 로깅해 주세요.

Suggested change
"Failed to re-fetch parents for copied file. fileId={}, cause={}",
newFileId, ex.getMessage()
"Failed to re-fetch parents for copied file. fileId={}",
newFileId, ex

Copilot uses AI. Check for mistakes.
@JanooGwan JanooGwan merged commit 5e464eb into develop Mar 24, 2026
7 checks passed
@JanooGwan JanooGwan deleted the refactor/google-sheet-api-v4 branch March 24, 2026 08:07
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.

2 participants