Skip to content

[Network] #206 - 이미지 앨범 이동 및 복제 API 연결#208

Open
OneTen19 wants to merge 17 commits intodevelopfrom
network/#206-sprint4-api
Open

[Network] #206 - 이미지 앨범 이동 및 복제 API 연결#208
OneTen19 wants to merge 17 commits intodevelopfrom
network/#206-sprint4-api

Conversation

@OneTen19
Copy link
Copy Markdown
Member

@OneTen19 OneTen19 commented Apr 13, 2026

🌴 작업한 브랜치

network/#206-sprint4-api

✅ 작업한 내용

  • 사진 이동 및 복제 API를 붙였습니다! 저번 피알 리뷰대로 담당 리듀서를 분리하니 붙이기가 되게 쉽군여 굳

📟 관련 이슈

Summary by CodeRabbit

새로운 기능

  • 선택한 사진을 여러 앨범으로 동시에 복제할 수 있습니다.
  • 선택한 사진을 다른 앨범으로 이동할 수 있습니다.

개선사항

  • 앨범 선택 및 사진 가져오기 기능이 실제 API를 통해 동작하도록 개선되었습니다.

@OneTen19 OneTen19 added this to the 4차 스프린트 milestone Apr 13, 2026
@OneTen19 OneTen19 requested a review from Remaked-Swain April 13, 2026 16:02
@OneTen19 OneTen19 self-assigned this Apr 13, 2026
@OneTen19 OneTen19 added the Network 📡 서버 통신 label Apr 13, 2026
@OneTen19 OneTen19 linked an issue Apr 13, 2026 that may be closed by this pull request
2 tasks
@OneTen19 OneTen19 added the 한열 🧄 한열 작업 label Apr 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

요약

사진 복제 및 이동 기능을 위한 새로운 API 엔드포인트 및 비즈니스 로직이 추가되었습니다. 네트워크 계층부터 프레젠테이션 계층까지 duplicatePhotomovePhoto 작업이 구현되었으며, 두 기능 모두 적절한 캐시 무효화 및 에러 처리를 포함합니다.

변경 사항

코호트 / 파일(들) 요약
네트워크 엔드포인트
Neki-iOS/Features/Archive/Sources/Data/Sources/ArchiveEndpoint.swift
duplicatePhoto(request:)movePhoto(request:) 케이스 추가. 경로 매핑: folders/photos/copy (POST), folders/photos/move (PATCH). 요청 본문에 해당 DTO 반환.
데이터 전송 객체
Neki-iOS/Features/Archive/Sources/Data/Sources/DTO/UpdateMappingPhotoDTO.swift
새로운 파일: UpdateMappingPhotoDTO enum 정의. MovePhotos(sourceFolderID, photoIDS, targetFolderIDS)와 DuplicatePhotos(photoIDS, targetFolderIDS) struct 추가. CodingKeys로 JSON 직렬화 맵핑.
저장소 구현
Neki-iOS/Features/Archive/Sources/Data/Sources/DefaultArchiveRepository.swift
duplicatePhoto()movePhoto() 메서드 추가. 네트워크 요청 후 앨범 캐시 무효화(isAlbumCacheDirty = true) 및 대상/출처 폴더의 사진 캐시 무효화 처리.
저장소 프로토콜
Neki-iOS/Features/Archive/Sources/Domain/Sources/Interfaces/Repositories/ArchiveRepository.swift
프로토콜에 duplicatePhoto()movePhoto() 메서드 선언 추가.
클라이언트 인터페이스
Neki-iOS/Features/Archive/Sources/Domain/Sources/Client/ArchiveClient.swift
duplicatePhotomovePhoto 프로퍼티 추가. DependencyKey 구현에서 저장소 메서드로 라우팅.
프레젠테이션 로직
Neki-iOS/Features/Archive/Sources/Presentation/Sources/Feature/AlbumSelectionFeature.swift
확인 버튼 동작 구현: 목적별(.duplicate/.move)으로 해당 클라이언트 메서드 호출. 에러 발생 시 .taskFailed 디스패치, 성공 시 .taskCompleted 디스패치.
포토 임포트 로직
Neki-iOS/Features/Archive/Sources/Presentation/Sources/Feature/PhotoImportFeature.swift
archiveClient.duplicatePhoto() 호출로 실제 복제 동작 구현. 성공/실패 처리 추가. fetchFavoritePhotoList() 호출 형식 개선.
뷰 정리
Neki-iOS/Features/Archive/Sources/Presentation/Sources/View/ArchiveAlbumDetailView.swift
TODO 주석 제거 (기능 변경 없음).

시퀀스 다이어그램

sequenceDiagram
    actor User
    participant Presentation as AlbumSelectionFeature
    participant Client as ArchiveClient
    participant Repository as DefaultArchiveRepository
    participant Network as NetworkProvider

    User->>Presentation: 확인 버튼 탭 (목적: duplicate/move)
    activate Presentation
    
    alt Purpose == .duplicate
        Presentation->>Client: duplicatePhoto(photoIDs, targetFolderIDs)
        activate Client
        Client->>Repository: duplicatePhoto(photoIDs, targetFolderIDs)
        activate Repository
        Repository->>Network: request(.duplicatePhoto(request))
        activate Network
        Network-->>Repository: 성공
        deactivate Network
        Repository->>Repository: isAlbumCacheDirty = true<br/>targetFolderIDs 캐시 무효화
        Repository-->>Client: void
        deactivate Repository
        Client-->>Presentation: void
        deactivate Client
        Presentation->>Presentation: .taskCompleted(복제 성공 메시지)
    else Purpose == .move
        Presentation->>Client: movePhoto(sourceFolderId, photoIDs, targetFolderIDs)
        activate Client
        Client->>Repository: movePhoto(sourceFolderId, photoIDs, targetFolderIDs)
        activate Repository
        Repository->>Network: request(.movePhoto(request))
        activate Network
        Network-->>Repository: 성공
        deactivate Network
        Repository->>Repository: isAlbumCacheDirty = true<br/>sourceFolderId + targetFolderIDs 캐시 무효화
        Repository-->>Client: void
        deactivate Repository
        Client-->>Presentation: void
        deactivate Client
        Presentation->>Presentation: .taskCompleted(이동 성공 메시지)
    end
    
    deactivate Presentation
Loading

예상 코드 리뷰 노력

🎯 3 (보통) | ⏱️ ~20분

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 PR의 주요 변경사항을 명확하게 반영하고 있으며, 이동 및 복제 API 연결이라는 핵심 내용을 간결하게 표현했습니다.
Description check ✅ Passed PR 설명이 작업 브랜치, 작업 내용, 관련 이슈를 포함하고 있으나, PR Point 섹션이 비어있고 스크린샷이 첨부되지 않았습니다.
Linked Issues check ✅ Passed PR의 모든 코드 변경사항이 #206의 요구사항(이동 API 및 복제 API 연결)을 충족하며, 각 플로우 케이스에 적절하게 적용되었습니다.
Out of Scope Changes check ✅ Passed ArchiveAlbumDetailView.swift의 TODO 주석 제거를 제외하고 모든 변경사항이 이동/복제 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 network/#206-sprint4-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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
Neki-iOS/Features/Archive/Sources/Data/Sources/DefaultArchiveRepository.swift (1)

327-345: movePhotofavoritePhotoCache 정합성 고려

사진 이동 시 favoritePhotoCache에 캐시된 PhotoEntityfolderID가 이전 폴더 ID를 유지할 수 있습니다. 즐겨찾기된 사진이 이동될 경우 캐시 불일치가 발생할 수 있습니다.

♻️ 캐시 무효화 추가 제안
 func movePhoto(sourceFolderId: Int, photoIDs: [Int], targetFolderIDs: [Int]) async throws {
     let request = UpdateMappingPhotoDTO.MovePhotos(
         sourceFolderID: sourceFolderId,
         photoIDS: photoIDs,
         targetFolderIDS: targetFolderIDs
     )
     let endpoint = ArchiveEndpoint.movePhoto(request: request)
     let _ = try await networkProvider.request(endpoint: endpoint)
     
     // 앨범 정보 캐시 갱신
     self.isAlbumCacheDirty = true
     
     self.isPhotoCacheDirty[sourceFolderId] = true
     
     // Target 폴더(혹은 전체 사진) 캐시 갱신
     for targetID in targetFolderIDs {
         self.isPhotoCacheDirty[targetID] = true
     }
+    
+    // 즐겨찾기 캐시에 이동된 사진이 있을 경우 대비
+    self.isFavoriteCacheDirty = true
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Neki-iOS/Features/Archive/Sources/Data/Sources/DefaultArchiveRepository.swift`
around lines 327 - 345, movePhoto currently invalidates album/photo caches but
doesn’t reconcile favoritePhotoCache, which can leave PhotoEntity.folderID
stale; inside movePhoto (function movePhoto) either update favoritePhotoCache
entries for each moved photoID (find entries in favoritePhotoCache whose id is
in photoIDs and set their folderID to the new target folderID when there is a
single target) or, if multiple/ambiguous targets, remove/invalidate those
favoritePhotoCache entries (or set a favorite cache dirty flag like
isFavoriteCacheDirty) so the favorites reload; reference favoritePhotoCache,
PhotoEntity.folderID, photoIDs and targetFolderIDs and apply the chosen fix
right after marking isPhotoCacheDirty so favorites remain consistent.
Neki-iOS/Features/Archive/Sources/Data/Sources/DTO/UpdateMappingPhotoDTO.swift (1)

11-29: 명명 규칙 고려: IDSIDs

Swift 명명 규칙에서 약어는 전부 대문자 또는 전부 소문자로 표기하는 것이 일반적입니다. photoIDStargetFolderIDS보다는 photoIDstargetFolderIDs가 더 Swift 관용적입니다.

♻️ 제안된 수정
 public struct MovePhotos: Encodable {
     let sourceFolderID: Int
-    let photoIDS, targetFolderIDS: [Int]
+    let photoIDs, targetFolderIDs: [Int]
     
     enum CodingKeys: String, CodingKey {
         case sourceFolderID = "sourceFolderId"
-        case photoIDS = "photoIds"
-        case targetFolderIDS = "targetFolderIds"
+        case photoIDs = "photoIds"
+        case targetFolderIDs = "targetFolderIds"
     }
 }

 public struct DuplicatePhotos: Encodable {
-    let photoIDS, targetFolderIDS: [Int]
+    let photoIDs, targetFolderIDs: [Int]

     enum CodingKeys: String, CodingKey {
-        case photoIDS = "photoIds"
-        case targetFolderIDS = "targetFolderIds"
+        case photoIDs = "photoIds"
+        case targetFolderIDs = "targetFolderIds"
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Neki-iOS/Features/Archive/Sources/Data/Sources/DTO/UpdateMappingPhotoDTO.swift`
around lines 11 - 29, Rename the properties to follow Swift idioms by changing
photoIDS → photoIDs, targetFolderIDS → targetFolderIDs, and sourceFolderID →
sourceFolderId in the MovePhotos and DuplicatePhotos structs (and update their
CodingKeys cases to still map to "photoIds", "targetFolderIds",
"sourceFolderId"); then update all usages/imports of MovePhotos,
DuplicatePhotos, photoIDS, targetFolderIDS, and sourceFolderID throughout the
codebase to the new names.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@Neki-iOS/Features/Archive/Sources/Data/Sources/DefaultArchiveRepository.swift`:
- Around line 327-345: movePhoto currently invalidates album/photo caches but
doesn’t reconcile favoritePhotoCache, which can leave PhotoEntity.folderID
stale; inside movePhoto (function movePhoto) either update favoritePhotoCache
entries for each moved photoID (find entries in favoritePhotoCache whose id is
in photoIDs and set their folderID to the new target folderID when there is a
single target) or, if multiple/ambiguous targets, remove/invalidate those
favoritePhotoCache entries (or set a favorite cache dirty flag like
isFavoriteCacheDirty) so the favorites reload; reference favoritePhotoCache,
PhotoEntity.folderID, photoIDs and targetFolderIDs and apply the chosen fix
right after marking isPhotoCacheDirty so favorites remain consistent.

In
`@Neki-iOS/Features/Archive/Sources/Data/Sources/DTO/UpdateMappingPhotoDTO.swift`:
- Around line 11-29: Rename the properties to follow Swift idioms by changing
photoIDS → photoIDs, targetFolderIDS → targetFolderIDs, and sourceFolderID →
sourceFolderId in the MovePhotos and DuplicatePhotos structs (and update their
CodingKeys cases to still map to "photoIds", "targetFolderIds",
"sourceFolderId"); then update all usages/imports of MovePhotos,
DuplicatePhotos, photoIDS, targetFolderIDS, and sourceFolderID throughout the
codebase to the new names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c141b88d-bc5a-4c23-81b5-82d1ae1b05fe

📥 Commits

Reviewing files that changed from the base of the PR and between bdcf41d and 0c5db09.

📒 Files selected for processing (8)
  • Neki-iOS/Features/Archive/Sources/Data/Sources/ArchiveEndpoint.swift
  • Neki-iOS/Features/Archive/Sources/Data/Sources/DTO/UpdateMappingPhotoDTO.swift
  • Neki-iOS/Features/Archive/Sources/Data/Sources/DefaultArchiveRepository.swift
  • Neki-iOS/Features/Archive/Sources/Domain/Sources/Client/ArchiveClient.swift
  • Neki-iOS/Features/Archive/Sources/Domain/Sources/Interfaces/Repositories/ArchiveRepository.swift
  • Neki-iOS/Features/Archive/Sources/Presentation/Sources/Feature/AlbumSelectionFeature.swift
  • Neki-iOS/Features/Archive/Sources/Presentation/Sources/Feature/PhotoImportFeature.swift
  • Neki-iOS/Features/Archive/Sources/Presentation/Sources/View/ArchiveAlbumDetailView.swift

Copy link
Copy Markdown
Member

@Remaked-Swain Remaked-Swain left a comment

Choose a reason for hiding this comment

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

특이사항 없어보입니다! LGTM
라인별 코멘트만 확인해주세용

그리고 API 나오기 이전 스프린트3, 4에 대한 피드백도 차차 고려해봐주세요.

Comment on lines +10 to +30
public enum UpdateMappingPhotoDTO {
public struct MovePhotos: Encodable {
let sourceFolderID: Int
let photoIDS, targetFolderIDS: [Int]

enum CodingKeys: String, CodingKey {
case sourceFolderID = "sourceFolderId"
case photoIDS = "photoIds"
case targetFolderIDS = "targetFolderIds"
}
}

public struct DuplicatePhotos: Encodable {
let photoIDS, targetFolderIDS: [Int]

enum CodingKeys: String, CodingKey {
case photoIDS = "photoIds"
case targetFolderIDS = "targetFolderIds"
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

p2)

팀 내 컨벤션에 따르면 식별자의 복수형에 대하여 ID는 대문자로, 뒤의 s는 소문자로 작성되어야 합니다.

Comment on lines +155 to +168
if purpose == .duplicate {
// 복제 - sourceFolderId 없음
try await archiveClient.duplicatePhoto(photoIDs: photoIDs, targetFolderIDs: targetFolderIDs)
await send(.taskCompleted(message: "사진을 앨범에 추가했어요"))

} else if purpose == .move {
// 이동 - sourceFolderId 필수
if let sourceFolderId = currentAlbumId {
try await archiveClient.movePhoto(sourceFolderId: sourceFolderId, photoIDs: photoIDs, targetFolderIDs: targetFolderIDs)
await send(.taskCompleted(message: "사진을 앨범으로 이동했어요"))
} else {
await send(.taskFailed(message: "사진 이동에 실패했어요"))
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

p3)

switch 문으로 바꾸면 연산횟수가 줄어들 수 있을까요?

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

Labels

Network 📡 서버 통신 한열 🧄 한열 작업

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Network] Sprint4 API 붙이기

2 participants