Skip to content

In 349 연동된 채널 삭제 api -> develop#220

Merged
MINUUUUUUUUUUUU merged 3 commits into
developfrom
in-349-연동된-채널-삭제-api
Jul 2, 2026

Hidden character warning

The head ref may contain hidden characters: "in-349-\uc5f0\ub3d9\ub41c-\ucc44\ub110-\uc0ad\uc81c-api"
Merged

In 349 연동된 채널 삭제 api -> develop#220
MINUUUUUUUUUUUU merged 3 commits into
developfrom
in-349-연동된-채널-삭제-api

Conversation

@MINUUUUUUUUUUUU

@MINUUUUUUUUUUUU MINUUUUUUUUUUUU commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Issue

#201


comment

요청에 맞춰 채널 연동 해제 기능을 구현했습니다.

  • DELETE /api/v1/channels/connect/{channelId} 엔드포인트 추가
  • 연동 해제 시 채널 행을 삭제하지 않고 user = null 처리
  • 해제된 채널(user_id = NULL)을 재연동할 때 새 행을 생성하지 않고 기존 행의 user를 복구

Summary by CodeRabbit

  • New Features
    • “채널 연동 해제” 기능이 추가되어, 채널을 해제할 수 있게 되었습니다.
    • 채널 동기화 시 사용자에게 미할당 상태로 남아있던 채널도 다시 사용자와 연결되도록 개선되었습니다.
  • Bug Fixes
    • 채널 해제 요청에서 채널이 존재하지 않거나 권한이 없는 경우의 예외 처리와 안내가 강화되었습니다.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b42f0cb-fd32-49a9-b9fa-e1a927eb1569

📥 Commits

Reviewing files that changed from the base of the PR and between 470d82d and 715b123.

📒 Files selected for processing (1)
  • src/main/java/com/example/inflace/domain/channel/service/ChannelService.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/example/inflace/domain/channel/service/ChannelService.java

Walkthrough

채널 연동 해제 API가 추가되어 채널의 user 연관을 해제할 수 있게 되었고, 동기화 시에는 user가 없는 기존 채널을 재사용하도록 upsert 로직이 바뀌었습니다.

Changes

채널 연동 해제 및 재연결 로직

Layer / File(s) Summary
엔티티 및 리포지토리 기반 변경
src/main/java/com/example/inflace/domain/channel/domain/Channel.java, src/main/java/com/example/inflace/domain/channel/repository/ChannelRepository.java
ChannelupdateUser(User user)가 추가되고, ChannelRepositoryfindByYoutubeChannelIdAndUserIsNull 조회 메서드가 추가되었습니다.
채널 연동 해제 서비스 로직
src/main/java/com/example/inflace/domain/channel/service/ChannelService.java
validateChannelExists 헬퍼가 제거되고, @Transactional disconnectChannel(Long channelId)가 추가되어 채널 조회, CHANNEL_NOT_FOUND 예외 처리, 소유자 검증, updateUser(null) 호출을 수행합니다.
연동 해제 API 및 컨트롤러 엔드포인트
src/main/java/com/example/inflace/domain/channel/controller/ChannelApi.java, src/main/java/com/example/inflace/domain/channel/controller/ChannelController.java
ChannelApi에 Swagger 메타데이터가 포함된 disconnectChannel 선언이 추가되고, ChannelControllerDELETE /connect/{channelId} 핸들러가 추가되어 channelService.disconnectChannel을 호출합니다.
유튜브 채널 동기화 재연결 로직
src/main/java/com/example/inflace/domain/channel/service/sync/YoutubeChannelDataPersistenceService.java
upsertChannel에서 사용자별 채널을 못 찾으면 user가 null인 기존 채널을 먼저 조회해 재사용하고, 없을 때만 새 채널을 생성합니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChannelController
  participant ChannelService
  participant ChannelRepository
  participant Channel

  ChannelController->>ChannelService: disconnectChannel(channelId)
  ChannelService->>ChannelRepository: findById(channelId)
  ChannelRepository-->>ChannelService: Channel or empty
  alt 채널 없음 또는 user 없음
    ChannelService-->>ChannelController: CHANNEL_NOT_FOUND 예외
  else 존재
    ChannelService->>ChannelService: validateChannelOwnership
    ChannelService->>Channel: updateUser(null)
    ChannelService-->>ChannelController: 처리 완료
  end
Loading
sequenceDiagram
  participant YoutubeChannelDataPersistenceService
  participant ChannelRepository
  participant Channel

  YoutubeChannelDataPersistenceService->>ChannelRepository: findByUser_IdAndYoutubeChannelId(userId, item.id)
  ChannelRepository-->>YoutubeChannelDataPersistenceService: 없음
  YoutubeChannelDataPersistenceService->>ChannelRepository: findByYoutubeChannelIdAndUserIsNull(item.id)
  alt user 없는 채널 존재
    ChannelRepository-->>YoutubeChannelDataPersistenceService: existing channel
    YoutubeChannelDataPersistenceService->>Channel: updateUser(user)
    YoutubeChannelDataPersistenceService->>YoutubeChannelDataPersistenceService: updateChannel(channel, item)
  else 없음
    YoutubeChannelDataPersistenceService->>Channel: Channel.builder() 로 신규 생성
  end
Loading

Possibly related PRs

Poem

나는 토끼, 채널 끈을 살짝 풀었지 🐰
user는 null, 마음도 가볍게
오래된 채널은 다시 손잡고
새 연결은 조용히 제자리를 찾고
폴짝, 리뷰도 깔끔하게 끝!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 채널 연동 해제 API 추가라는 변경 사항과 대체로 일치하며, 핵심 기능을 잘 드러냅니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 in-349-연동된-채널-삭제-api

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In
`@src/main/java/com/example/inflace/domain/channel/repository/ChannelRepository.java`:
- Line 14: `ChannelRepository.findByYoutubeChannelIdAndUserIsNull` assumes a
single match, but the current data model can produce multiple null-user rows for
the same YouTube channel. Update the contract to reflect that reality by either
enforcing uniqueness in the schema/domain for the null-user case or changing the
repository and reconnect flow to handle multiple results explicitly. Use the
existing `ChannelRepository` method and the reconnect logic that calls it to
either return a collection and clean up duplicates, or guarantee a single row
before keeping `Optional`.

In
`@src/main/java/com/example/inflace/domain/channel/service/ChannelService.java`:
- Around line 380-389: `disconnectChannel` leaves a channel row with `user =
null`, but the shared ownership check still dereferences
`channel.getUser().getId()` and can throw NPE on later lookups. Update the
common validation path in `ChannelService` (especially
`validateChannelOwnership` and any caller like `disconnectChannel`) to handle a
null `getUser()` before accessing the user ID, and return the existing
channel/not-found or forbidden error instead of allowing a 500.

In
`@src/main/java/com/example/inflace/domain/channel/service/sync/YoutubeChannelDataPersistenceService.java`:
- Around line 83-87: `YoutubeChannelDataPersistenceService`의
`findByYoutubeChannelIdAndUserIsNull(item.id())` 기반 재할당은 동시 요청에서 같은 disconnected
`Channel`을 두 번 읽어 소유자 덮어쓰기가 발생할 수 있으므로 보호가 필요합니다. `updateUser(user)`로 들어가기 전에 이
조회를 `PESSIMISTIC_WRITE` 락이 걸린 조회로 바꾸거나, 한 번만 claim 되는 원자적 업데이트/조회 방식으로 변경해
`channelRepository` 경로가 동시에 같은 row를 재할당하지 못하게 하세요.
🪄 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: e96baa42-6d26-486e-b6e3-ec8fa73c85b7

📥 Commits

Reviewing files that changed from the base of the PR and between 4a38cb3 and 470d82d.

📒 Files selected for processing (6)
  • src/main/java/com/example/inflace/domain/channel/controller/ChannelApi.java
  • src/main/java/com/example/inflace/domain/channel/controller/ChannelController.java
  • src/main/java/com/example/inflace/domain/channel/domain/Channel.java
  • src/main/java/com/example/inflace/domain/channel/repository/ChannelRepository.java
  • src/main/java/com/example/inflace/domain/channel/service/ChannelService.java
  • src/main/java/com/example/inflace/domain/channel/service/sync/YoutubeChannelDataPersistenceService.java

Optional<Channel> findByUser_Id(UUID userId);
Optional<Channel> findByUser_IdAndYoutubeChannelId(UUID userId, String youtubeChannelId);
Optional<Channel> findByYoutubeChannelId(String youtubeChannelId);
Optional<Channel> findByYoutubeChannelIdAndUserIsNull(String youtubeChannelId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Optional 전제가 현재 데이터 모델과 맞지 않습니다.

지금 스키마는 (user_id, youtube_channel_id)만 유니크라서, 같은 youtube_channel_id에 대해 user_id = NULL인 행이 여러 개 생길 수 있습니다. 게다가 재연동 로직은 "현재 사용자 소유 행 없음 + null-user 행 없음"이면 새 row를 저장하므로, 동일 유튜브 채널을 여러 사용자가 연결했다가 모두 해제하면 이 메서드가 단건이 아니라 다건을 맞게 됩니다. 그 상태에서 재연동은 Optional 조회 단계에서 깨지거나 임의의 행을 재사용하게 됩니다. DB에서 이 상태의 유일성을 강제하거나, 조회 계약을 다건 처리로 바꿔 정리 로직을 두는 쪽으로 맞춰야 합니다.

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

In
`@src/main/java/com/example/inflace/domain/channel/repository/ChannelRepository.java`
at line 14, `ChannelRepository.findByYoutubeChannelIdAndUserIsNull` assumes a
single match, but the current data model can produce multiple null-user rows for
the same YouTube channel. Update the contract to reflect that reality by either
enforcing uniqueness in the schema/domain for the null-user case or changing the
repository and reconnect flow to handle multiple results explicitly. Use the
existing `ChannelRepository` method and the reconnect logic that calls it to
either return a collection and clean up duplicates, or guarantee a single row
before keeping `Optional`.

Comment on lines +83 to +87
.orElseGet(() -> channelRepository.findByYoutubeChannelIdAndUserIsNull(item.id())
.map(channel -> {
channel.updateUser(user);
return updateChannel(channel, item);
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -i 'Channel.java|ChannelRepository.java' src/main/java \
  --exec sh -c 'echo "### $1"; rg -n -C3 "`@Version`|`@Lock`|findByYoutubeChannelIdAndUserIsNull|youtubeChannelId|uniqueConstraints|class Channel|interface ChannelRepository" "$1"' sh {}

Repository: JECT-Study/inflace-4th-server

Length of output: 2016


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  src/main/java/com/example/inflace/domain/channel/domain/Channel.java \
  src/main/java/com/example/inflace/domain/common/domain/BaseTimeEntity.java \
  src/main/java/com/example/inflace/domain/channel/repository/ChannelRepository.java \
  src/main/java/com/example/inflace/domain/channel/service/sync/YoutubeChannelDataPersistenceService.java
do
  echo "### $f"
  sed -n '1,220p' "$f"
  echo
done

echo "### version/lock search"
rg -n "`@Version`|`@Lock`|PESSIMISTIC_WRITE|OPTIMISTIC|findByYoutubeChannelIdAndUserIsNull|user_id|youtube_channel_id" src/main/java src/main/resources

Repository: JECT-Study/inflace-4th-server

Length of output: 2857


user IS NULL 재할당에 락이 필요합니다.

동시 재연동 요청이 같은 disconnected row를 동시에 읽으면 둘 다 updateUser(user)까지 진행해 마지막 커밋이 소유자를 덮어쓸 수 있습니다. Channel@Version도 없고 이 조회에도 락이 없어서, PESSIMISTIC_WRITE나 원자적 claim 쿼리로 보호해야 합니다.

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

In
`@src/main/java/com/example/inflace/domain/channel/service/sync/YoutubeChannelDataPersistenceService.java`
around lines 83 - 87, `YoutubeChannelDataPersistenceService`의
`findByYoutubeChannelIdAndUserIsNull(item.id())` 기반 재할당은 동시 요청에서 같은 disconnected
`Channel`을 두 번 읽어 소유자 덮어쓰기가 발생할 수 있으므로 보호가 필요합니다. `updateUser(user)`로 들어가기 전에 이
조회를 `PESSIMISTIC_WRITE` 락이 걸린 조회로 바꾸거나, 한 번만 claim 되는 원자적 업데이트/조회 방식으로 변경해
`channelRepository` 경로가 동시에 같은 row를 재할당하지 못하게 하세요.

@HanYoonSoo HanYoonSoo left a comment

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.

고생하셨습니다~~!!

@MINUUUUUUUUUUUU
MINUUUUUUUUUUUU merged commit 79afeb0 into develop Jul 2, 2026
3 checks passed
@MINUUUUUUUUUUUU
MINUUUUUUUUUUUU deleted the in-349-연동된-채널-삭제-api branch July 2, 2026 05:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants