Skip to content

Conversation

@youz2me
Copy link
Member

@youz2me youz2me commented Aug 19, 2025

👻 PULL REQUEST

📄 작업 내용

  • 기존에 파편화되어 있던 Content, Comment 엔티티를 ContentTemp, CommentTemp(임시 네이밍)로 통합했어요.
  • 좋아요 기능을 Likable 프로토콜을 통해 구현하고, ContentTemp, CommentTemp가 각각 Likable 프로토콜을 따르도록 리팩토링했어요.
  • 홈, 상세, 프로필 화면에서 사용되던 Content, Comment 관련 엔티티를 삭제하고 ContentTemp, CommentTemp로 변환했어요.

💻 주요 코드 설명

파편화된 Content 관련 엔티티 통합

  • 기존에는 서버 구조를 반영해 각각 Content, UserContent, ContentInfo로 게시물 엔티티가 파편화되어 있었습니다.
    • 서버 통신에는 유리한 구조였으나 코드 작성 시 중복 정의 이슈로 유지보수성과 가독성이 떨어지는 문제가 발생했습니다.
  • 이를 해결하기 위해 Content, UserContent, ContentInfo를 단일 엔티티인 ContentTemp로 통합하고 관리함으로써 데이터 변환 로직을 단순화하고, 확장성과 유지보수성을 높였습니다.
// MARK: - 게시물 리스트

struct Content: Hashable {
    let content: UserContent
    let isDeleted: Bool
}

struct UserContent: Identifiable, Hashable {
    let id: Int
    let contentInfo: ContentTemp
}

// MARK: - 게시물 상세 정보

struct ContentInfo: Hashable {
    let author: User
    let createdDate: Date?
    let title: String
    let imageURL: URL?
    let text: String
    
    var status: PostStatus
    var like: Like
    var opacity: Opacity
    var commentCount: Int
}

// MARK: - 위 엔티티를 단일화한 게시물 정보 엔티티 (임시)

struct ContentTemp: Identifiable, Hashable, Likable {
    let id: Int
    let author: User
    let text: String
    let title: String
    let imageURL: URL?
    let isDeleted: Bool?
    let createdDate: Date?
    
    var isLiked: Bool
    var likeCount: Int
    var opacity: Opacity
    var commentCount: Int
    var status: PostStatus
}

효율적인 좋아요 상태 관리를 위한 Likable 프로토콜 도입

  • 새로 도입한 ContentTemp, CommentTemp는 모두 좋아요 기능을 가지고 있습니다.
  • 좋아요 기능을 가진 엔티티는 기본적으로 isLiked, likedCount 프로퍼티를 가져 상태를 관리할 수 있어야 합니다.
    • 이 상태를 Likable이라는 프로토콜을 통해 관리하고 프로토콜 익스텐션 내 mutating function을 통해 좋아요, 좋아요 취소 기능을 구현함으로써 보일러 플레이트 코드를 제거하고, 기능 확장성을 높였습니다.
import Foundation

protocol Likable {
    var isLiked: Bool { get set }
    var likeCount: Int { get set }
    
    mutating func like()
    mutating func unlike()
}

extension Likable {
    mutating func like() {
        isLiked = true
        likeCount += 1
    }
    
    mutating func unlike() {
        isLiked = false
        if likeCount > 0 {
            likeCount -= 1
        }
    }
}

👀 리뷰어에게 전달할 사항

  • 현재 바뀐 코드 컨벤션은 다른 브랜치에서 작업중이라 적용되지 않은 상황입니다. 컨벤션은 일단 이번 PR에서는 눈감아주시기를(ㅎㅎ) 부탁드립니닷.
  • 작업하고 계실 것 같아서 병합 후 브랜치는 삭제하지 않겠습니다!
  • 엔티티 같이 합칠 때 설명을 들었는데 맨날 까먹어서 PR에 자세히 적어봤습니다. 우하핫 김진웅 최고

✅ 이번 PR에서 이런 부분을 중점적으로 체크해주세요!

  • x

🔗 연결된 이슈

Summary by CodeRabbit

  • 새로운 기능
    • 좋아요 상태와 카운트 관리를 위한 Likable 프로토콜 추가.
  • 리팩터링
    • 포스트/목록/상세/프로필 전반의 도메인 모델을 단일화한 ContentTemp로 통합.
    • 댓글 처리용 임시 모델 CommentTemp 추가.
    • UI·뷰모델·유스케이스·리포지토리 전반에서 새 모델로 필드 접근 및 액션 경로 정리.
  • 기타
    • 도메인 구조에 Protocol 그룹 추가로 프로젝트 구성 개선.

@youz2me youz2me requested a review from JinUng41 August 19, 2025 06:35
@youz2me youz2me self-assigned this Aug 19, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 19, 2025

Walkthrough

도메인 모델을 ContentTemp/CommentTemp로 전환하고 Likable 프로토콜을 추가했으며, 매퍼·레포지토리·유즈케이스·프레젠테이션 전반에서 타입·시그니처·셀·퍼블리셔를 ContentTemp/Comment.swift/Likable.swift 기반으로 갱신하고 Xcode 프로젝트 파일 참조를 조정했습니다. (약 50단어)

Changes

Cohort / File(s) Summary
프로젝트 파일
Wable-iOS.xcodeproj/project.pbxproj
Domain/Protocol 그룹 추가 및 Likable.swift 파일/빌드 등록; Domain/Entity/CommentInfo.swift 참조 제거 및 Comment.swift로 교체.
도메인 엔티티
Wable-iOS/Domain/Entity/...
Wable-iOS/Domain/Entity/Content.swift, Wable-iOS/Domain/Entity/Comment.swift
ContentTemp(Identifiable, Hashable, Likable) 추가 및 기존 UserContent/ContentInfo 기반 속성들 → ContentTemp로 전환. CommentTemp: Likable 추가.
프로토콜
Wable-iOS/Domain/Protocol/Likable.swift
Likable 프로토콜 추가(가변 isLiked/likeCount + mutating like()/unlike() 기본 구현).
매퍼
Wable-iOS/Data/Mapper/ContentMapper.swift
모든 매핑을 평탄화된 ContentTemp로 반환하도록 변경; 단건 매핑에 id 파라미터 추가.
레포지토리 (인터페이스 & 구현)
Wable-iOS/Domain/RepositoryInterface/ContentRepository.swift, Wable-iOS/Data/RepositoryImpl/ContentRepositoryImpl.swift
fetchContentInfo/fetchContentList/fetchUserContentList 등 반환 타입을 ContentTemp/[ContentTemp]로 변경; 단건 매핑 호출을 toDomain(response, id)로 변경.
유즈케이스
Wable-iOS/Domain/UseCase/...
FetchContentListUseCase, FetchContentInfoUseCase, FetchUserContentListUseCase 등 반환 타입을 ContentTemp/[ContentTemp]로 변경(로직 유지).
프레젠테이션 — 홈
Wable-iOS/Presentation/Home/..., Wable-iOS/Presentation/WableComponent/Cell/ContentCollectionViewCell.swift
HomeViewController, HomeDetailViewController, 뷰모델, 셀 구성 등에서 Content/ContentInfoContentTemp로 전환. 데이터 소스 제네릭·퍼블리셔 시그니처·셀 파라미터·액션 핸들러 접근 경로 업데이트.
프레젠테이션 — 프로필
Wable-iOS/Presentation/Profile/...
ProfileViewItem.contentList, My/Other Profile 뷰·뷰모델에서 UserContentContentTemp로 변경. 좋아요 토글 등은 Likable 기반으로 조정.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor U as User
    participant HV as HomeViewController
    participant HVM as HomeViewModel
    participant UC as FetchContentListUseCase
    participant CR as ContentRepository
    participant NW as Network/API
    participant MP as ContentMapper

    U->>HV: 목록 요청
    HV->>HVM: fetchContents(cursor)
    HVM->>UC: execute(cursor)
    UC->>CR: fetchContentList(cursor)
    CR->>NW: GET /contents?cursor=...
    NW-->>CR: [DTO]
    CR->>MP: toDomain([DTO])
    MP-->>CR: [ContentTemp]
    CR-->>UC: [ContentTemp]
    UC-->>HVM: [ContentTemp]
    HVM-->>HV: Output.contents([ContentTemp])
    HV->>HV: Diffable DataSource 업데이트
Loading
sequenceDiagram
    autonumber
    actor U as User
    participant HDV as HomeDetailViewController
    participant HDM as HomeDetailViewModel
    participant UCP as FetchContentInfoUseCase
    participant CR as ContentRepository
    participant NW as Network/API
    participant MP as ContentMapper

    U->>HDV: 상세 진입(contentID)
    HDV->>HDM: fetchContent(contentID)
    HDM->>UCP: execute(contentID)
    UCP->>CR: fetchContentInfo(contentID)
    CR->>NW: GET /contents/{id}
    NW-->>CR: FetchContent DTO
    CR->>MP: toDomain(dto, id)
    MP-->>CR: ContentTemp
    CR-->>UCP: ContentTemp
    UCP-->>HDM: ContentTemp
    HDM-->>HDV: Output.content(ContentTemp?)
    HDV->>Cell: configureCell(info: ContentTemp)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
content-comment 통합 (#266) PR은 Content/Comment 모델을 각각 ContentTemp/CommentTemp로 변환하고 Likable을 도입했음. 이슈의 "통합"이 의미하는 단일 합병(예: Content와 Comment의 구조적 병합 또는 통합 API 규격)은 명시되어 있지 않아 완전 충족 여부 불분명.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#266-content-comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or `` to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

Caution

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

🔭 Outside diff range comments (4)
Wable-iOS/Presentation/Home/View/HomeViewController.swift (3)

180-196: 셀에 저장되는 클로저가 self/cell을 강하게 잡아 순환 참조 및 메모리 누수 가능성

contentImageViewTapHandler와 likeButtonTapHandler가 cell과 self를 강하게 캡처합니다. 셀은 이 핸들러를 프로퍼티로 오래 보유하므로 VC ↔ CollectionView ↔ Cell ↔ Closure ↔ VC의 사이클이 형성될 수 있습니다.

아래처럼 약한 캡처로 교체해 주세요.

-                contentImageViewTapHandler: {
-                    guard let image = cell.contentImageView.image else {
-                        return
-                    }
-                    
-                    self.navigationController?.pushViewController(PhotoDetailViewController(image: image), animated: true)
-                },
-                likeButtonTapHandler: {
-                    AmplitudeManager.shared.trackEvent(tag: .clickLikePost)
-                    
-                    self.didHeartTappedSubject.send((item.id, cell.likeButton.isLiked))
-                },
+                contentImageViewTapHandler: { [weak self, weak cell] in
+                    guard let self = self,
+                          let image = cell?.contentImageView.image else { return }
+                    self.navigationController?.pushViewController(PhotoDetailViewController(image: image), animated: true)
+                },
+                likeButtonTapHandler: { [weak self, weak cell] in
+                    AmplitudeManager.shared.trackEvent(tag: .clickLikePost)
+                    guard let self = self,
+                          let isLiked = cell?.likeButton.isLiked else { return }
+                    self.didHeartTappedSubject.send((item.id, isLiked))
+                },

196-263: 설정 버튼 핸들러에서도 self를 약하게 캡처해 순환 참조 차단 필요

설정 시트 액션 핸들러는 self를 다수 참조합니다. 약한 캡처와 초기 guard로 누수 가능성을 줄이세요.

아래처럼 변경을 권장합니다.

-                settingButtonTapHandler: {
-                    let viewController = WableBottomSheetController()
+                settingButtonTapHandler: { [weak self] in
+                    guard let self = self else { return }
+                    let viewController = WableBottomSheetController()
@@
-                    if self.activeUserID == item.author.id {
+                    if self.activeUserID == item.author.id {
@@
-                    } else if self.isActiveUserAdmin ?? false {
+                    } else if self.isActiveUserAdmin ?? false {
@@
-                    } else {
+                    } else {
@@
-                    self.present(viewController, animated: true)
+                    self.present(viewController, animated: true)
                 },

288-296: 고스트 버튼 핸들러도 self 약한 캡처 필요

UI 액션을 보관하는 셀 클로저 특성상 self를 약하게 캡처해야 안전합니다.

-                ghostButtonTapHandler: {
+                ghostButtonTapHandler: { [weak self] in
-                    AmplitudeManager.shared.trackEvent(tag: .clickGhostPost)
-                    self.showGhostSheet(onCancel: {
+                    AmplitudeManager.shared.trackEvent(tag: .clickGhostPost)
+                    self?.showGhostSheet(onCancel: {
-                        AmplitudeManager.shared.trackEvent(tag: .clickWithdrawghostPopup)
-                    }, onPrimary: { message in
-                        AmplitudeManager.shared.trackEvent(tag: .clickApplyghostPopup)
-                        self.didGhostTappedSubject.send((item.id, item.author.id, message))
+                        AmplitudeManager.shared.trackEvent(tag: .clickWithdrawghostPopup)
+                    }, onPrimary: { message in
+                        AmplitudeManager.shared.trackEvent(tag: .clickApplyghostPopup)
+                        self?.didGhostTappedSubject.send((item.id, item.author.id, message))
                     })
                 }
Wable-iOS/Presentation/Profile/Other/View/OtherProfileViewController.swift (1)

207-218: 미사용 indexPath 파라미터와 셀 보관 클로저의 강한 캡처 수정 필요

  • indexPath는 미사용이므로 _ 처리 권장.
  • contentImageViewTapHandler가 cell을 강하게 캡처합니다. [weak cell]로 순환 참조를 방지하세요.
-        let contentCellRegistration = CellRegistration<ContentCollectionViewCell, ContentTemp> {
-            cell, indexPath, item in
+        let contentCellRegistration = CellRegistration<ContentCollectionViewCell, ContentTemp> {
+            cell, _, item in
             cell.configureCell(
                 info: item,
                 authorType: .others,
                 cellType: .list,
-                contentImageViewTapHandler: { [weak self] in
-                    guard let image = cell.contentImageView.image else { return }
+                contentImageViewTapHandler: { [weak self, weak cell] in
+                    guard let image = cell?.contentImageView.image else { return }
                     
                     let photoDetailViewController = PhotoDetailViewController(image: image)
                     self?.navigationController?.pushViewController(photoDetailViewController, animated: true)
                 },
🧹 Nitpick comments (12)
Wable-iOS/Domain/Protocol/Likable.swift (1)

19-31: 토글 편의 메서드 제공을 제안합니다

호출부 단순화를 위해 toggleLike() 제공을 고려해 보세요. 중복 분기 로직을 줄일 수 있습니다.

 extension Likable {
     mutating func like() {
@@
     mutating func unlike() {
@@
     }
+
+    mutating func toggleLike() {
+        isLiked ? unlike() : like()
+    }
 }
Wable-iOS/Domain/Entity/Content.swift (2)

19-20: UserContent.contentInfo → ContentTemp로의 전환은 타당합니다만, 프로퍼티명은 여전히 contentInfo입니다

타입이 ContentTemp로 바뀌었으니 프로퍼티명도 content 등으로 정리하면 가독성이 좋아집니다. 지금 상태는 타입/이름 불일치로 혼동 여지가 있습니다. 다음 단계에서 네이밍 변경을 고려해 주세요.

해당 네이밍 변경 시 호출부 영향 범위가 넓을 수 있으니, 매퍼/뷰모델/셀 구성부까지 빌드 검증 부탁드립니다.


39-53: ContentTemp 도입은 일관되고 깔끔합니다

Identifiable/Hashable/Likable 채택으로 프리젠테이션과 상호작용(좋아요)까지 한 타입에 일원화된 점이 좋습니다.

한 가지 확인: title이 비Optional(String)입니다. 서버/DTO에서 title이 없을 수 있는 케이스가 없다면 OK입니다. 존재할 수 있다면 매핑 시 강제 언래핑이나 임의 기본값 주입 여부를 점검해주세요. 또한 Wrapper인 Content.isDeleted와 ContentTemp.isDeleted(옵셔널)로 삭제 상태의 이중 소스가 생길 수 있습니다. 궁극적으로는 Wrapper 제거 또는 단일 소스 유지로 정리하는 것이 안전합니다.

Wable-iOS/Presentation/Profile/Model/ProfileViewItem.swift (1)

10-15: commentList는 여전히 구 타입(UserComment)입니다 — CommentTemp로 동기화 권장

콘텐츠 리스트가 ContentTemp로 통합된 것과 달리 댓글 리스트는 UserComment(내부에 CommentInfo)로 남아있어 두 모델이 혼재합니다. 일관성·매핑 복잡성을 고려하면 [CommentTemp]로의 통일을 권장합니다.

아래처럼 모델을 교체할 경우, 해당 뷰모델/유즈케이스/매퍼의 반환 타입과 셀 바인딩을 함께 점검해 주세요.

struct ProfileViewItem {
    var currentSegment: ProfileSegment = .content
    var profileInfo: UserProfile?
    var contentList: [ContentTemp] = []
    var commentList: [CommentTemp] = [] // 제안
}
Wable-iOS/Presentation/Home/View/HomeViewController.swift (2)

176-179: 미사용 파라미터(indexPath)로 인한 SwiftLint 경고 제거 제안

cell registration 클로저에서 indexPath를 사용하지 않습니다. 린트 경고(unused_closure_parameter) 제거를 위해 _로 변경을 권장합니다.

아래처럼 수정해 주세요.

-        let homeCellRegistration = CellRegistration<ContentCollectionViewCell, ContentTemp> {
-            [weak self] cell, indexPath, item in
+        let homeCellRegistration = CellRegistration<ContentCollectionViewCell, ContentTemp> {
+            [weak self] cell, _, item in

448-455: DiffableDataSource 적용 후 reloadData 호출은 불필요 — 성능/애니메이션 개선

snapshot 적용만으로 UI 반영이 충분합니다. reloadData는 불필요한 전체 리로드를 유발해 성능과 애니메이션에 악영향이 있습니다.

         dataSource?.apply(snapshot)
-        collectionView.reloadData()
Wable-iOS/Presentation/WableComponent/Cell/ContentCollectionViewCell.swift (1)

274-274: 옵셔널 처리 개선 제안

createdDate가 nil일 경우 함수가 조기 종료되어 셀이 구성되지 않습니다. 이는 UI에 빈 셀이 표시될 수 있습니다.

-        guard let createdDate = info.createdDate else { return }
+        let createdDate = info.createdDate ?? Date()

또는 더 명시적인 처리:

-        guard let createdDate = info.createdDate else { return }
+        guard let createdDate = info.createdDate else {
+            // 날짜가 없는 경우에도 최소한의 UI는 표시
+            infoView.isHidden = true
+            return
+        }
Wable-iOS/Presentation/Profile/Other/ViewModel/OtherProfileViewModel.swift (1)

89-91: 좋아요 상태 업데이트 로직 개선 필요

SwiftLint 경고에 따라 삼항 연산자를 사용하여 void 함수를 호출하는 것은 권장되지 않습니다. 더 명확한 if-else 구문을 사용하세요.

 await MainActor.run {
     var contentInfo = item.contentList[index]
-    isLiked ? contentInfo.unlike() : contentInfo.like()
+    if isLiked {
+        contentInfo.unlike()
+    } else {
+        contentInfo.like()
+    }
     item.contentList[index] = contentInfo
 }
Wable-iOS/Presentation/Home/ViewModel/HomeDetailViewModel.swift (1)

329-342: 중복된 ContentTemp 생성 로직

Ghost 상태 업데이트 시 ContentTemp 생성 로직이 중복되어 있습니다. 헬퍼 메서드로 추출하여 재사용성을 높이세요.

ContentTemp 생성 로직을 헬퍼 메서드로 추출:

private func createUpdatedContent(
    from content: ContentTemp,
    status: PostStatus? = nil,
    opacity: Opacity? = nil
) -> ContentTemp {
    return ContentTemp(
        id: content.id,
        author: content.author,
        text: content.text,
        title: content.title,
        imageURL: content.imageURL,
        isDeleted: content.isDeleted,
        createdDate: content.createdDate,
        isLiked: content.isLiked,
        likeCount: content.likeCount,
        opacity: opacity ?? content.opacity,
        commentCount: content.commentCount,
        status: status ?? content.status
    )
}

그리고 사용:

-let updatedContent = ContentTemp(
-    id: content.id,
-    author: content.author,
-    text: content.text,
-    title: content.title,
-    imageURL: content.imageURL,
-    isDeleted: content.isDeleted,
-    createdDate: content.createdDate,
-    isLiked: content.isLiked,
-    likeCount: content.likeCount,
-    opacity: content.opacity,
-    commentCount: content.commentCount,
-    status: .ghost
-)
+let updatedContent = createUpdatedContent(from: content, status: .ghost)
Wable-iOS/Presentation/Home/ViewModel/HomeViewModel.swift (3)

160-187: 좋아요 상태 업데이트 로직 개선 가능

ContentTemp 생성 로직이 반복되고 있습니다. 헬퍼 메서드를 사용하여 코드 중복을 줄이세요.

private func updateContentLikeStatus(
    _ content: ContentTemp,
    isLiked: Bool
) -> ContentTemp {
    return ContentTemp(
        id: content.id,
        author: content.author,
        text: content.text,
        title: content.title,
        imageURL: content.imageURL,
        isDeleted: content.isDeleted,
        createdDate: content.createdDate,
        isLiked: isLiked,
        likeCount: isLiked ? content.likeCount + 1 : content.likeCount - 1,
        opacity: content.opacity,
        commentCount: content.commentCount,
        status: content.status
    )
}

201-201: SwiftLint 경고: where 절 사용 권장

for 루프 내부의 단일 if 문 대신 where 절을 사용하세요.

-for i in 0..<updatedContents.count {
-    if updatedContents[i].author.id == userID {
+for i in 0..<updatedContents.count where updatedContents[i].author.id == userID {

Also applies to: 271-271


270-292: 밴 처리 시 중복 코드

Ghost 처리와 유사한 로직이 반복됩니다. 공통 헬퍼 메서드로 추출하세요.

private func updateContentsStatus(
    _ contents: [ContentTemp],
    for userID: Int,
    status: PostStatus
) -> [ContentTemp] {
    return contents.map { content in
        if content.author.id == userID {
            return ContentTemp(
                id: content.id,
                author: content.author,
                text: content.text,
                title: content.title,
                imageURL: content.imageURL,
                isDeleted: content.isDeleted,
                createdDate: content.createdDate,
                isLiked: content.isLiked,
                likeCount: content.likeCount,
                opacity: content.opacity.reduced(),
                commentCount: content.commentCount,
                status: status
            )
        }
        return content
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4e4ff3f and 6ca177b.

📒 Files selected for processing (20)
  • Wable-iOS.xcodeproj/project.pbxproj (8 hunks)
  • Wable-iOS/Data/Mapper/ContentMapper.swift (4 hunks)
  • Wable-iOS/Data/RepositoryImpl/ContentRepositoryImpl.swift (3 hunks)
  • Wable-iOS/Domain/Entity/Comment.swift (2 hunks)
  • Wable-iOS/Domain/Entity/Content.swift (2 hunks)
  • Wable-iOS/Domain/Protocol/Likable.swift (1 hunks)
  • Wable-iOS/Domain/RepositoryInterface/ContentRepository.swift (1 hunks)
  • Wable-iOS/Domain/UseCase/Home/FetchContentListUseCase.swift (1 hunks)
  • Wable-iOS/Domain/UseCase/Profile/FetchContentInfoUseCase.swift (1 hunks)
  • Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (1 hunks)
  • Wable-iOS/Presentation/Home/View/HomeDetailViewController.swift (13 hunks)
  • Wable-iOS/Presentation/Home/View/HomeViewController.swift (9 hunks)
  • Wable-iOS/Presentation/Home/ViewModel/HomeDetailViewModel.swift (8 hunks)
  • Wable-iOS/Presentation/Home/ViewModel/HomeViewModel.swift (8 hunks)
  • Wable-iOS/Presentation/Profile/Model/ProfileViewItem.swift (1 hunks)
  • Wable-iOS/Presentation/Profile/My/View/MyProfileViewController.swift (2 hunks)
  • Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift (3 hunks)
  • Wable-iOS/Presentation/Profile/Other/View/OtherProfileViewController.swift (3 hunks)
  • Wable-iOS/Presentation/Profile/Other/ViewModel/OtherProfileViewModel.swift (3 hunks)
  • Wable-iOS/Presentation/WableComponent/Cell/ContentCollectionViewCell.swift (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (12)
Wable-iOS/Domain/UseCase/Profile/FetchContentInfoUseCase.swift (4)
Wable-iOS/Domain/UseCase/Home/FetchContentListUseCase.swift (1)
  • execute (22-24)
Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (1)
  • execute (18-24)
Wable-iOS/Domain/UseCase/Home/CreateContentUseCase.swift (1)
  • execute (23-25)
Wable-iOS/Domain/UseCase/Home/DeleteContentUseCase.swift (1)
  • execute (23-25)
Wable-iOS/Presentation/Profile/My/View/MyProfileViewController.swift (1)
Wable-iOS/Presentation/WableComponent/Cell/ContentCollectionViewCell.swift (1)
  • configureCell (257-341)
Wable-iOS/Domain/RepositoryInterface/ContentRepository.swift (1)
Wable-iOS/Data/RepositoryImpl/ContentRepositoryImpl.swift (4)
  • fetchContentInfo (57-64)
  • fetchContentList (66-73)
  • fetchUserContentList (75-82)
  • fetchUserContentList (84-94)
Wable-iOS/Presentation/WableComponent/Cell/ContentCollectionViewCell.swift (1)
Wable-iOS/Presentation/WableComponent/Button/LikeButton.swift (1)
  • configureButton (47-70)
Wable-iOS/Presentation/Home/ViewModel/HomeViewModel.swift (2)
Wable-iOS/Presentation/Home/ViewModel/HomeDetailViewModel.swift (1)
  • transform (91-512)
Wable-iOS/Domain/Entity/Opacity.swift (1)
  • reduced (72-78)
Wable-iOS/Presentation/Profile/Other/View/OtherProfileViewController.swift (1)
Wable-iOS/Presentation/WableComponent/Cell/ContentCollectionViewCell.swift (1)
  • configureCell (257-341)
Wable-iOS/Presentation/Home/View/HomeViewController.swift (1)
Wable-iOS/Presentation/WableComponent/Cell/ContentCollectionViewCell.swift (1)
  • configureCell (257-341)
Wable-iOS/Presentation/Home/ViewModel/HomeDetailViewModel.swift (5)
Wable-iOS/Domain/UseCase/Profile/FetchContentInfoUseCase.swift (1)
  • execute (22-24)
Wable-iOS/Domain/UseCase/Home/FetchContentListUseCase.swift (1)
  • execute (22-24)
Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (1)
  • execute (18-24)
Wable-iOS/Domain/UseCase/Home/FetchGhostUseCase.swift (1)
  • execute (23-30)
Wable-iOS/Data/Mapper/ErrorMapper.swift (1)
  • map (29-43)
Wable-iOS/Presentation/Profile/Other/ViewModel/OtherProfileViewModel.swift (1)
Wable-iOS/Data/RepositoryImpl/ContentRepositoryImpl.swift (2)
  • fetchUserContentList (75-82)
  • fetchUserContentList (84-94)
Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (5)
Wable-iOS/Domain/UseCase/Profile/FetchContentInfoUseCase.swift (1)
  • execute (22-24)
Wable-iOS/Domain/UseCase/Home/FetchContentListUseCase.swift (1)
  • execute (22-24)
Wable-iOS/Domain/UseCase/Profile/FetchUserCommentListUseCase.swift (1)
  • execute (18-24)
Wable-iOS/Domain/UseCase/Profile/FetchAccountInfoUseCase.swift (1)
  • execute (18-20)
Wable-iOS/Domain/UseCase/Profile/FetchUserProfileUseCase.swift (1)
  • execute (18-24)
Wable-iOS/Data/RepositoryImpl/ContentRepositoryImpl.swift (2)
Wable-iOS/Data/Mapper/ErrorMapper.swift (2)
  • map (29-43)
  • mapWableError (13-25)
Wable-iOS/Data/Mapper/ContentMapper.swift (3)
  • toDomain (13-51)
  • toDomain (53-93)
  • toDomain (95-135)
Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift (1)
Wable-iOS/Data/RepositoryImpl/ContentRepositoryImpl.swift (2)
  • fetchUserContentList (75-82)
  • fetchUserContentList (84-94)
🪛 SwiftLint (0.57.0)
Wable-iOS/Presentation/Profile/My/View/MyProfileViewController.swift

[Warning] 209-209: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

Wable-iOS/Presentation/Home/ViewModel/HomeViewModel.swift

[Warning] 201-201: where clauses are preferred over a single if inside a for

(for_where)


[Warning] 271-271: where clauses are preferred over a single if inside a for

(for_where)

Wable-iOS/Presentation/Profile/Other/View/OtherProfileViewController.swift

[Warning] 208-208: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

Wable-iOS/Presentation/Home/View/HomeViewController.swift

[Warning] 177-177: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

Wable-iOS/Presentation/Profile/Other/ViewModel/OtherProfileViewModel.swift

[Warning] 90-90: Using ternary to call Void functions should be avoided

(void_function_in_ternary)

Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift

[Warning] 121-121: Using ternary to call Void functions should be avoided

(void_function_in_ternary)

🔇 Additional comments (25)
Wable-iOS.xcodeproj/project.pbxproj (1)

165-166: 빌드 설정 연결 확인 완료 (Likable.swift/Comment.swift 등록 정상)

  • Likable.swift가 파일 참조 및 Sources 빌드 단계에 정상 추가되었습니다.
  • CommentInfo.swift → Comment.swift로의 참조 교체도 정상입니다.

특이사항은 없습니다. 머지해도 빌드 단계에서 문제 없을 것으로 보입니다.

혹시 남아있는 경로/심볼로 CommentInfo를 참조하는 곳이 있다면 정리 부탁드립니다(문자열 경로 하드코딩 등).

Also applies to: 172-172, 543-543, 557-557, 2952-2952

Wable-iOS/Presentation/Profile/Model/ProfileViewItem.swift (1)

13-13: Profile 화면에서 ContentTemp로 전환 LGTM

contentList를 [ContentTemp]로 바꿔 프리젠테이션 모델과 도메인 모델이 일치합니다. 이후 셀/뷰모델에서 Likable의 isLiked/likeCount를 직접 활용할 수 있어 단순해집니다.

Wable-iOS/Domain/UseCase/Home/FetchContentListUseCase.swift (1)

22-24: 콘텐츠 리스트 반환 타입 전환 OK

리스트 유스케이스도 ContentTemp로 일관되게 마이그레이션되었습니다. Repository 위임 로직도 그대로여서 리스크 낮습니다.

Wable-iOS/Presentation/Home/View/HomeViewController.swift (1)

22-22: 타입 별칭 전환 적절

Home 화면의 아이템 모델을 ContentTemp로 통일한 선택이 명확합니다. 이후 셀 구성, 상세 전환 등과 자연스럽게 맞물립니다.

Wable-iOS/Presentation/Profile/My/View/MyProfileViewController.swift (1)

26-26: Item.case content의 ContentTemp 전환 적절

프로필 화면 아이템 모델도 ContentTemp로 통일되어 상위 레이어와 일관성이 확보되었습니다.

Wable-iOS/Presentation/Profile/Other/View/OtherProfileViewController.swift (1)

25-25: Item.case content의 ContentTemp 전환 적절

Other 프로필 화면도 ContentTemp로 정리되어 셀/VM/유즈케이스와 정합성이 좋습니다.

Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (1)

12-12: 좋은 리팩토링입니다!

UserContent에서 통합된 ContentTemp 타입으로의 마이그레이션이 적절하게 이루어졌습니다. 이는 코드베이스 전반에 걸쳐 일관된 도메인 모델을 사용하도록 하는 좋은 개선사항입니다.

Also applies to: 18-18

Wable-iOS/Presentation/WableComponent/Cell/ContentCollectionViewCell.swift (1)

258-258: ContentTemp로의 타입 변경이 적절합니다!

ContentInfo에서 ContentTemp로의 마이그레이션과 함께 like 정보 접근 방식이 info.like.status/info.like.count에서 info.isLiked/info.likeCount로 단순화되었습니다. 이는 Likable 프로토콜과 일관된 구조입니다.

Also applies to: 332-332

Wable-iOS/Presentation/Profile/My/ViewModel/MyProfileViewModel.swift (2)

109-109: Likable 프로토콜 활용이 훌륭합니다!

ContentTemplike()/unlike() 메서드를 활용한 좋아요 상태 업데이트가 깔끔하게 구현되었습니다. 이는 Likable 프로토콜의 장점을 잘 활용한 좋은 예시입니다.

Also applies to: 120-122


164-167: 타입 변경이 올바르게 적용되었습니다

[UserContent]에서 [ContentTemp]로의 타입 변경이 repository 호출부터 UI 업데이트까지 일관되게 적용되었습니다.

Wable-iOS/Domain/RepositoryInterface/ContentRepository.swift (1)

16-19: Repository 인터페이스 통합이 잘 이루어졌습니다!

모든 fetch 메서드가 통합된 ContentTemp 타입을 반환하도록 변경되어 도메인 모델의 일관성이 향상되었습니다. Combine과 async/await 버전 모두 적절히 업데이트되었습니다.

Wable-iOS/Data/Mapper/ContentMapper.swift (3)

13-13: id 파라미터 추가가 적절합니다

단일 콘텐츠 fetch 시 응답에서 ID를 가져올 수 없는 경우를 위해 별도 파라미터로 받는 것은 좋은 설계입니다.


32-50: ContentTemp로의 매핑 로직이 일관되게 구현되었습니다!

세 가지 toDomain 메서드 모두 새로운 ContentTemp 구조로 일관되게 매핑되었습니다. 특히 like 정보가 최상위 레벨 필드(isLiked, likeCount)로 평탄화되어 더 직관적인 구조가 되었습니다.

Also applies to: 73-91, 115-133


43-43: isDeleted 필드 매핑 일관성 검토 필요

ContentMapper.swift에서 isDeleted를 다음과 같이 다르게 처리하고 있습니다:

  • 라인 43 (FetchContent 매핑): isDeleted: nil — DTO.Response.FetchContent에는 isDeleted 필드가 존재
  • 라인 84 (FetchContents 매핑): isDeleted: content.isDeleted — DTO.Response.FetchContents의 isDeleted 값을 그대로 사용
  • 라인 126 (FetchUserContents 매핑): isDeleted: nil — DTO.Response.FetchUserContents에는 isDeleted 필드가 없음

위 차이가 의도된 동작인지, DTO 계층과 도메인 매핑에서 삭제 상태를 올바르게 반영하고 있는지 확인 부탁드립니다.

Wable-iOS/Presentation/Profile/Other/ViewModel/OtherProfileViewModel.swift (2)

78-78: 현재 구현이 정확합니다

ContentTempisLiked 프로퍼티 접근이 올바르게 구현되어 있습니다.


227-230: ContentTemp 타입 마이그레이션 확인됨

fetchUserContentList가 올바르게 [ContentTemp]를 반환하도록 업데이트되었고, 타입 선언이 일관되게 적용되었습니다.

Wable-iOS/Presentation/Home/ViewModel/HomeDetailViewModel.swift (2)

80-80: ContentTemp로의 타입 마이그레이션 완료

Output 구조체의 content 퍼블리셔와 내부 contentSubject가 모두 ContentTemp? 타입으로 올바르게 업데이트되었습니다.

Also applies to: 92-92


189-202: ContentTemp 생성자 호출 인수 순서 일치 확인
ContentTemp의 선언된 멤버 순서(id, author, text, title, imageURL, isDeleted, createdDate, isLiked, likeCount, opacity, commentCount, status)와 비교한 결과, 생성자 호출이 정확히 일치합니다. 수정이 필요 없습니다.

Wable-iOS/Presentation/Home/ViewModel/HomeViewModel.swift (2)

60-61: ContentTemp 타입 마이그레이션 성공적으로 완료

Public API가 올바르게 ContentTemp로 업데이트되었고, 내부 subject도 일관되게 변경되었습니다.

Also applies to: 68-68


135-135: 페이지네이션 커서 업데이트 확인

lastItem.id를 커서로 사용하도록 올바르게 변경되었습니다. ContentTemp의 id 필드가 페이지네이션에 적합한지 확인이 필요합니다.

Wable-iOS/Data/RepositoryImpl/ContentRepositoryImpl.swift (2)

57-64: ContentMapper.toDomain 호출 시그니처 변경 확인

ContentMapper.toDomain($0, contentID) 호출이 두 개의 파라미터를 받도록 변경되었습니다. 매퍼의 구현이 이를 올바르게 처리하는지 확인됨.


75-82: fetchUserContentList 메서드 일관성 확인

Publisher와 async 버전 모두 [ContentTemp]를 반환하도록 올바르게 업데이트되었고, 에러 매핑도 적절히 처리되었습니다.

Also applies to: 84-94

Wable-iOS/Presentation/Home/View/HomeDetailViewController.swift (3)

25-26: ContentTemp로의 타입 마이그레이션 완료

Item enum과 cell registration이 올바르게 ContentTemp를 사용하도록 업데이트되었습니다.

Also applies to: 205-206


217-218: authorType 계산 로직 간소화됨

item.author.id를 직접 사용하여 코드가 더 간결해졌습니다.


775-782: updateContent 메서드 시그니처 업데이트 확인

메서드가 올바르게 ContentTemp 타입을 받도록 업데이트되었습니다.

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: 0

♻️ Duplicate comments (1)
Wable-iOS/Domain/Entity/Comment.swift (1)

41-55: children가 구 모델(ContentComment)을 참조합니다 — [CommentTemp]로 통일하고 식별/해시 가능하도록 보강 권장

통합 목표(Temp 기반 단일 모델)와 상충합니다. 트리 구조에서 타입 혼재는 매핑/표현 계층 복잡도와 상태 불일치 리스크를 키웁니다. 또한 SwiftUI/콜렉션 디프/세트 사용을 고려하면 Identifiable/Hashable 채택이 필요합니다. 아래처럼 정리해 주세요.

-struct CommentTemp: Likable {
+struct CommentTemp: Identifiable, Hashable, Likable {
     let author: User
     let text: String
     let commentID: Int
     let contentID: Int
     let isDeleted: Bool?
     let createdDate: Date?
     let parentContentID: Int?
-    let children: [ContentComment]?
+    let children: [CommentTemp]?
     
     var likeCount: Int
     var isLiked: Bool
     var opacity: Opacity
     var status: PostStatus
+    var id: Int { commentID }
 }

추가로, 전역 코드에서 ContentComment 기반 children 접근을 사용하는 곳이 있다면 함께 치환이 필요합니다. 원하시면 전역 리팩터링 패치 제안 가능합니다.

🧹 Nitpick comments (3)
Wable-iOS/Domain/Entity/Comment.swift (3)

49-49: 옵셔널 children 대신 빈 배열이 호출부를 단순화합니다

nil 체크를 줄이고 forEach/collection 디프 사용성을 높이기 위해 기본값이 빈 배열인 비옵셔널을 권장합니다.

-    let children: [CommentTemp]?
+    let children: [CommentTemp]

46-46: isDeleted는 Bool(비옵셔널) 일관화를 권장

동일 파일 내 ContentComment는 Bool을 사용합니다(라인 35). 도메인 의미가 “삭제 여부”라면 비옵셔널 Bool이 적합합니다. 매퍼에서 기본값(false) 보장 혹은 명시적 세팅을 권장합니다.

-    let isDeleted: Bool?
+    let isDeleted: Bool

48-48: CommentTemp의 parentContentID 네이밍 수정 제안

parentContentID는 실제로 “부모 댓글 ID”를 나타내므로, parentCommentID(또는 도메인 ContentComment와 통일하려면 parentID)로 변경하는 것이 명확합니다.

– 변경 대상:
• 파일: Wable-iOS/Domain/Entity/Comment.swift
• 구조체: CommentTemp
• 프로퍼티: parentContentID: Int?parentCommentID: Int?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6ca177b and af0785c.

📒 Files selected for processing (1)
  • Wable-iOS/Domain/Entity/Comment.swift (2 hunks)
🔇 Additional comments (1)
Wable-iOS/Domain/Entity/Comment.swift (1)

39-39: 섹션 마킹 추가는 가독성에 도움 됩니다

임시 모델 블록을 명확히 구분해 주어 탐색성이 좋아졌습니다.

@Team-Wable Team-Wable deleted a comment from coderabbitai bot Aug 19, 2025
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: 0

Caution

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

⚠️ Outside diff range comments (1)
Wable-iOS/Presentation/Home/View/HomeDetailViewController.swift (1)

216-352: 셀 내부 핸들러 클로저에서 self 강참조 → 순환 참조/메모리 릭 위험

cell.configureCell에 전달되는 각 핸들러(이미지 탭, 좋아요, 설정, 프로필, 고스트) 및 버튼 UIAction이 self를 강하게 캡처합니다. 셀/버튼이 클로저를 강하게 보관하면 VC ↔︎ 컬렉션뷰/셀 ↔︎ 클로저 ↔︎ VC 순환 참조가 발생할 수 있습니다. 각 핸들러에 [weak self] 캡처 리스트를 추가하고 초기에 guard로 self를 확보하는 패턴을 권장합니다. (commentCellRegistration 쪽 핸들러들도 동일한 패턴 적용을 권장)

아래는 핵심 시작 부분만 발췌한 패치 예시입니다.

-                contentImageViewTapHandler: {
+                contentImageViewTapHandler: { [weak self] in
+                    guard let self = self else { return }
                     guard let image = cell.contentImageView.image else { return }
                     let photoDetailViewController = PhotoDetailViewController(image: image)
                     self.navigationController?.pushViewController(photoDetailViewController, animated: true)
                 },
-                likeButtonTapHandler: {
+                likeButtonTapHandler: { [weak self] in
+                    guard let self = self else { return }
                     AmplitudeManager.shared.trackEvent(tag: .clickLikeComment)
                     self.didContentHeartTappedSubject.send(cell.likeButton.isLiked)
                 },
-                settingButtonTapHandler: {
+                settingButtonTapHandler: { [weak self] in
+                    guard let self = self else { return }
                     let viewController = WableBottomSheetController()
                     ...
                 },
-                profileImageViewTapHandler: {
-                    if self.activeUserID == item.author.id,
+                profileImageViewTapHandler: { [weak self] in
+                    guard let self = self else { return }
+                    if self.activeUserID == item.author.id,
                        let tabBarController = self.tabBarController {
                         tabBarController.selectedIndex = 4
                     } else {
                         let viewController = OtherProfileViewController(
                             viewModel: .init(
                                 userID: item.author.id,
                                 ...
                             ))
                         self.navigationController?.pushViewController(viewController, animated: true)
                     }
                 },
-                ghostButtonTapHandler: {
+                ghostButtonTapHandler: { [weak self] in
+                    guard let self = self else { return }
                     AmplitudeManager.shared.trackEvent(tag: .clickGhostPost)
                     self.showGhostSheet(onCancel: {
                         AmplitudeManager.shared.trackEvent(tag: .clickWithdrawghostPopup)
                     }, onPrimary: { message in
                         AmplitudeManager.shared.trackEvent(tag: .clickApplyghostPopup)
                         self.didGhostTappedSubject.send((item.id, item.author.id, message, .content))
                     })
                 }
             )
 
-            cell.commentButton.addAction(UIAction(handler: { _ in
+            cell.commentButton.addAction(UIAction(handler: { [weak self] _ in
+                guard let self = self else { return }
                 AmplitudeManager.shared.trackEvent(tag: .clickWriteComment)
                 self.createCommentButton.isEnabled = false
                 self.didCommentTappedSubject.send()
                 self.commentTextView.text = ""
                 self.updatePlaceholder(for: item.author.nickname, type: .ripple)
                 self.commentTextView.endEditing(true)
                 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                     self.commentTextView.becomeFirstResponder()
                 }
             }), for: .touchUpInside)

commentCellRegistration의 like/setting/profile/ghost/reply 핸들러들도 같은 방식으로 [weak self] 적용을 권장합니다.

🧹 Nitpick comments (2)
Wable-iOS/Presentation/Home/View/HomeDetailViewController.swift (2)

205-219: 셀 등록/바인딩의 ContentTemp 적용은 적절합니다 (경미한 가독성 제안 포함)

ContentCollectionViewCell 등록 제네릭과 configure 파라미터를 ContentTemp로 정리한 부분은 좋습니다. authorType 계산식은 Optional 비교를 명시적으로 표기하면 가독성이 조금 더 좋아집니다.

아래처럼 괄호로 의도를 드러내는 정도의 미세 수정 제안입니다.

-                authorType: item.author.id == self.activeUserID ? .mine : .others,
+                authorType: (self.activeUserID == item.author.id) ? .mine : .others,

583-601: ContentTemp 재생성 로직 단순화 + isDeleted 전달 여부 확인

현재 output.content에서 받은 content로 다시 ContentTemp를 구성하면서 isDeleted를 고정 false로 설정합니다. 원본의 삭제 상태를 UI에 반영해야 한다면, 이 값은 소스의 값을 전달해야 합니다. 또한 output.content가 이미 ContentTemp 타입이라면 굳이 재생성하지 않고 그대로 전달하는 편이 간결합니다.

옵션 A: output.content가 이미 ContentTemp인 경우 단순 통과

-                owner.updateContent(
-                    ContentTemp(
-                        id: content.id,
-                        author: content.author,
-                        text: content.text,
-                        title: content.title,
-                        imageURL: content.imageURL,
-                        isDeleted: false,
-                        createdDate: content.createdDate,
-                        isLiked: content.isLiked,
-                        likeCount: content.likeCount,
-                        opacity: content.opacity,
-                        commentCount: content.commentCount,
-                        status: content.status
-                    )
-                )
+                owner.updateContent(content)

옵션 B: 재생성이 필요하다면 isDeleted 전달 보정

-                        isDeleted: false,
+                        isDeleted: content.isDeleted,

참고: 이전 커밋에서 지적된 id 설정 이슈(activeUserID 사용)는 본 변경에서 content.id로 교정되어 적절합니다. 굿.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 76869a1 and 9ca7ef3.

📒 Files selected for processing (2)
  • Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift (1 hunks)
  • Wable-iOS/Presentation/Home/View/HomeDetailViewController.swift (13 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Wable-iOS/Domain/UseCase/Profile/FetchUserContentListUseCase.swift
🔇 Additional comments (2)
Wable-iOS/Presentation/Home/View/HomeDetailViewController.swift (2)

771-778: Diffable Snapshot 업데이트 로직은 간결하고 올바릅니다

콘텐츠 섹션 아이템을 전체 교체하는 방식이 UI 동기화 관점에서 명확합니다. 애니메이션 사용도 적절합니다.


25-27: ContentComment 사용 현황 및 CommentTemp 통합 전략 확인 필요

프로젝트 전반에서 ContentComment가 광범위하게 사용되고 있고, CommentTempDomain/Entity/Comment.swift에만 정의되어 있습니다.
이 PR의 의도가 단계적 리팩터링인지, 아니면 한 번에 CommentTemp로 통합하는 것인지 명확히 해주세요.

주요 사용 위치:

  • Presentation/Home/View/HomeDetailViewController.swift
    • case comment(ContentComment)
    • didCommentHeartTappedSubject: PassthroughSubject<(Bool, ContentComment), Never>
    • 콜렉션뷰 셀 등록 및 updateComments(_ comments: [ContentComment])
  • Presentation/Home/ViewModel/HomeDetailViewModel.swift
    • AnyPublisher<(Bool, ContentComment), Never>
    • AnyPublisher<[ContentComment], Never> 및 내부 매핑 로직 다수
  • Data/RepositoryImpl/CommentRepositoryImpl.swift
    • fetchContentCommentList 반환 타입
  • Data/Mapper/CommentMapper.swift
    • toDomain(_:)에서 ContentComment 생성
  • Domain/Entity/Comment.swift
    • struct ContentComment, struct CommentTemp 정의

만약 이 PR에서 댓글 타입을 CommentTemp로 한꺼번에 통합할 계획이라면, 위 위치들의 타입을 모두 교체해야 합니다.
단계적 전환이라면 PR 설명이나 코드 주석에 의도를 명시해 주세요.

Copy link
Collaborator

@JinUng41 JinUng41 left a comment

Choose a reason for hiding this comment

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

CommentTemp에 대해서는 아직 수정을 안하신거겠죠?

고생많으셨습니다.
많은 부분 헷갈리셨을수도 있는데 잘 해내신 것 같아요.
감사합니다!!

let isDeleted: Bool?
let createdDate: Date?
let parentContentID: Int?
let children: [ContentComment]?
Copy link
Collaborator

Choose a reason for hiding this comment

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

자식 댓글을 사용하지 않는 곳에서는 빈 배열로 관리해도 괜찮지 않을까 생각했는데, 어떠세요?

Copy link
Member Author

Choose a reason for hiding this comment

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

오....... 옵셔널 바인딩을 하지 않아도 돼서 조금 더 편할 것 같습니다. 반영해볼게요!!

@youz2me youz2me merged commit d93318e into develop Aug 20, 2025
1 check was pending
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: 1

♻️ Duplicate comments (1)
Wable-iOS/Domain/Entity/Comment.swift (1)

41-55: children에 구 모델이 혼재되어 트리 일관성이 깨집니다 — CommentTemp로 통일 및 Identifiable/Hashable 채택 권장

현재 CommentTemp.children이 [ContentComment]로 선언되어 있어 새 모델과 구 모델이 섞입니다. 트리 순회/매핑 시 타입 변환 오버헤드와 상태 불일치가 발생할 수 있어 본 PR 목적(통합)과 상충합니다. 또한 UI/컬렉션에서 안정적인 식별/해싱을 위해 Identifiable/Hashable 채택을 권장합니다. id는 commentID로 노출하면 됩니다. children은 과거 논의대로 non-optional로 두되 기본값 빈 배열을 제공하면 옵셔널 바인딩 제거 효과를 유지할 수 있습니다.

아래처럼 반영을 제안합니다:

-struct CommentTemp: Likable {
+struct CommentTemp: Identifiable, Hashable, Likable {
     let author: User
     let text: String
     let commentID: Int
     let contentID: Int
     let isDeleted: Bool?
     let createdDate: Date?
     let parentContentID: Int?
-    let children: [ContentComment]
+    let children: [CommentTemp] = []
     
     var likeCount: Int
     var isLiked: Bool
     var opacity: Opacity
     var status: PostStatus
+    
+    var id: Int { commentID }
 }

추가 메모:

  • 구 모델 ContentComment의 childs 네이밍과도 불일치(children vs childs)가 있어, 전역적으로 children으로 통일하면 검색/리팩터링 생산성이 올라갑니다.
🧹 Nitpick comments (2)
Wable-iOS/Domain/Entity/Comment.swift (2)

41-55: isDeleted 옵셔널 제거 및 기본값 false 권장

삭제 여부는 도메인상 3상태가 필요하지 않다면 Bool? 대신 Bool로 단순화하는 편이 안전하고 사용처에서의 불필요한 옵셔널 처리도 줄어듭니다. 서버 스키마가 비어 있을 수 있다면 매퍼에서 기본값 false를 주입하면 됩니다.

-    let isDeleted: Bool?
+    let isDeleted: Bool = false

41-55: 대댓글 동적 추가/삭제 플로우가 있다면 children을 var로 전환 고려

현재 children이 let이라 런타임에 대댓글을 추가하려면 전체 모델을 재생성해야 합니다. 이미 likeCount/isLiked는 var로 뮤터블하게 관리 중이라 일관성을 위해 children도 var로 두는 선택지가 있습니다. 반대로 완전 불변 모델을 지향한다면 like 관련 상태도 외부 스토어로 분리하는 쪽이 더 깔끔합니다.

-    let children: [CommentTemp] = []
+    var children: [CommentTemp] = []
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca7ef3 and 2ae253b.

📒 Files selected for processing (1)
  • Wable-iOS/Domain/Entity/Comment.swift (2 hunks)
🔇 Additional comments (1)
Wable-iOS/Domain/Entity/Comment.swift (1)

2-2: 파일 리네임/헤더 변경 문제 없습니다

파일명 갱신에 따른 헤더 변경으로 보이며, 별도 이슈 없어 보입니다.

Comment on lines +41 to +55
struct CommentTemp: Likable {
let author: User
let text: String
let commentID: Int
let contentID: Int
let isDeleted: Bool?
let createdDate: Date?
let parentContentID: Int?
let children: [ContentComment]

var likeCount: Int
var isLiked: Bool
var opacity: Opacity
var status: PostStatus
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

parentContentID의 의미 확인 필요 — 부모 ‘댓글’ ID인지 ‘게시물’ ID인지 검증 요청

필드명이 parentContentID라 상위 게시물 ID로 읽히지만, 대댓글 트리를 다룬다면 부모 댓글 ID(parentCommentID)가 필요할 수 있습니다. 의도된 스키마인지 확인 부탁드립니다. 만약 부모 댓글 ID라면 네이밍을 parentCommentID(또는 parentID)로 바꾸는 것이 정확합니다.

아래 스크립트로 레포 내 사용처를 훑어보면 혼용 여부를 빠르게 확인할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# parentContentID / parentCommentID 사용처와 문맥 확인
set -euo pipefail

echo "== References to parentContentID =="
rg -n -C3 --glob '!**/Pods/**' --glob '!**/DerivedData/**' '\bparentContentID\b'

echo
echo "== References to parentCommentID =="
rg -n -C3 --glob '!**/Pods/**' --glob '!**/DerivedData/**' '\bparentCommentID\b|\bparentID\b'

Length of output: 17163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== References to CommentTemp =="
rg -n -C3 --glob '!**/Pods/**' '\bCommentTemp\b'

Length of output: 549


CommentTemp의 parentContentID 네이밍 일관성 검토 및 수정 요망
CommentTempparentContentID가 실제로는 부모 댓글의 ID를 나타내는 것으로 보입니다. 도메인 모델(ContentComment)에서는 동일한 의미로 parentID를 사용하고 있으므로, 혼동을 줄이고 일관성을 유지하기 위해 아래와 같이 네이밍을 변경해 주세요.

  • 변경 위치
    • Wable-iOS/Domain/Entity/Comment.swift — struct CommentTemp

  • 수정 제안

    -    let parentContentID: Int?
    +    let parentID: Int?
    // 또는
    +    let parentCommentID: Int?

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Wable-iOS/Domain/Entity/Comment.swift around lines 41 to 55, the property
parentContentID on struct CommentTemp represents the parent comment ID but is
named inconsistently with ContentComment which uses parentID; rename
parentContentID to parentID (keeping the same type and optionality), update all
local references/usages, initializers, Codable/Decodable keys (or CodingKeys)
and any mapping/conversion code to use parentID, and run/adjust any callers or
tests to reflect the new name to maintain behavior and compile cleanly.

youz2me added a commit that referenced this pull request Oct 26, 2025
[Refactor] 파편화된 Content, Comment 엔티티 통합 및 홈, 상세, 프로필 화면에 적용 (Content)
@youz2me youz2me deleted the refactor/#266-content-comment branch November 18, 2025 17:15
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.

[Refactor] Content-Comment 통합

3 participants