Skip to content

refactor: UploadFileUseCase 리팩토링#373

Merged
hgjwilly-koreatech merged 1 commit intodevelopfrom
refactor/uploadFileUsecase
Mar 14, 2026
Merged

refactor: UploadFileUseCase 리팩토링#373
hgjwilly-koreatech merged 1 commit intodevelopfrom
refactor/uploadFileUsecase

Conversation

@hgjwilly-koreatech
Copy link
Contributor

@hgjwilly-koreatech hgjwilly-koreatech commented Mar 14, 2026

#️⃣연관된 이슈

📝작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)

기존 UploadFileUseCase는 Shop 도메인에 결합되어, domain이 항상 SHOP 입니다.
Core 도메인으로 변경하여, 사용하는 도메인마다 SHOP, LOST-ITEM 등을 domain 값으로 전달하도록 변경합니다.

스크린샷 (선택)

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

Summary by CodeRabbit

  • Refactor
    • Restructured file upload functionality to support domain-based uploads across multiple contexts (items, lands, circles, market, shops, members, owners, coop, admin, lost items, banner, club, and call van reports).
    • File uploads now require specifying a domain parameter to categorize uploaded content.

Core 도메인으로 분리
@hgjwilly-koreatech hgjwilly-koreatech self-assigned this Mar 14, 2026
@hgjwilly-koreatech hgjwilly-koreatech added the REFACTOR 리팩토링 label Mar 14, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

This PR migrates file upload functionality from the Shop domain to the Core domain by: (1) removing uploadFiles methods from ShopService/ShopRepository/ShopAPI; (2) adding uploadFiles methods to CoreService/CoreRepository/CoreAPI with domain parameter support; (3) introducing UploadDomain enum for domain categorization; (4) creating UploadFileUseCase in Core domain; (5) updating presentation layer to use core-based uploads with appropriate domains.

Changes

Cohort / File(s) Summary
Core Domain Upload Infrastructure
Koin/Domain/Repository/CoreRepository.swift, Koin/Data/Repository/DefaultCoreRepository.swift, Koin/Domain/Model/UploadDomain.swift
Added uploadFiles(files:domain:) method to CoreRepository protocol and implementation. Introduced UploadDomain enum with 14 domain categories (ITEMS, LANDS, CIRCLES, MARKET, SHOPS, etc.) to support categorized file uploads.
Core Service Upload Implementation
Koin/Data/Service/CoreService.swift, Koin/Data/Service/Network/API/CoreAPI.swift
Extended CoreService protocol and DefaultCoreService with uploadFiles(files:domain:) method. Added CoreAPI.uploadFiles case with multipart/form-data headers, POST method, and path routing to /{domain}/upload/files.
Shop Domain Upload Removal
Koin/Domain/Repository/ShopRepository.swift, Koin/Data/Repository/DefaultShopRepository.swift, Koin/Data/Service/ShopService.swift, Koin/Data/Service/Network/API/ShopAPI.swift
Removed uploadFiles method from ShopRepository protocol, DefaultShopRepository, ShopService protocol, and DefaultShopService. Removed uploadFiles case from ShopAPI enum and its associated routing logic.
NetworkService Upload Handler
Koin/Data/Service/Network/NetworkService.swift
Updated uploadFiles method signature to accept CoreAPI instead of ShopAPI. Modified guard pattern and implemented full multipart upload flow with Alamofire, response validation, and error handling.
Domain Use Case Refactoring
Koin/Domain/UseCase/Core/UploadFileUseCase.swift, Koin/Domain/UseCase/Shop/UploadFileUseCase.swift
Created new UploadFileUseCase protocol and DefaultUploadFileUseCase class in Core domain with CoreRepository dependency. Removed Shop domain's UploadFileUseCase protocol and implementation.
Presentation Layer View Models
Koin/Presentation/Chat/Chat/ChatViewModel.swift, Koin/Presentation/LostItem/EditLostItem/EditLostItemViewModel.swift, Koin/Presentation/LostItem/PostLostItem/PostLostItemViewModel.swift
Updated uploadFileUseCase initialization from shop-based to core-based. Changed execute calls to include domain parameter (.lostItem) for context-aware uploads.
Shop Presentation Layer Updates
Koin/Presentation/Shop/ShopReview/ShopReviewViewModel.swift, Koin/Presentation/Shop/ShopReviewList/ReviewListViewController.swift
Changed uploadFileUseCase property type to existential type (any UploadFileUseCase). Updated execute call with .shops domain parameter. Rewired DefaultUploadFileUseCase to use coreRepository instead of shopRepository.
Project Configuration
koin.xcodeproj/project.pbxproj
Added UploadDomain.swift and Core domain UploadFileUseCase.swift to build phases. Reorganized file references across Domain/Model and Domain/UseCase groups.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 Files now hop to the Core domain bright,
Where UploadDomain guides them right—
From Shop to Core, the path is clear,
Domain-aware uploads appear! 📤✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: refactoring UploadFileUseCase from Shop-specific to a generic Core domain implementation with parameterized domain support.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/uploadFileUsecase
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

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

🧹 Nitpick comments (4)
Koin/Presentation/LostItem/EditLostItem/EditLostItemViewModel.swift (1)

32-32: Same DI consideration as PostLostItemViewModel.

Inline instantiation reduces testability. If addressing this, consider making uploadFileUseCase injectable via the initializer.

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

In `@Koin/Presentation/LostItem/EditLostItem/EditLostItemViewModel.swift` at line
32, The field uploadFileUseCase is being instantiated inline which harms
testability; make it an injectable dependency by adding an initializer parameter
(e.g., uploadFileUseCase: UploadFileUseCase) to EditLostItemViewModel and assign
it to the uploadFileUseCase property instead of creating
DefaultUploadFileUseCase(DefaultCoreRepository(DefaultCoreService())) inside the
class; optionally provide a default parameter value that constructs
DefaultUploadFileUseCase(...) to preserve existing call sites while enabling
tests to inject a mock implementation.
Koin/Data/Service/Network/API/CoreAPI.swift (1)

42-49: Consider removing explicit Content-Type header for multipart uploads.

When using Alamofire's AF.upload(multipartFormData:...), the framework automatically sets the Content-Type header with the correct boundary parameter. Manually setting Content-Type: multipart/form-data without the boundary may cause server-side parsing issues.

♻️ Suggested fix
     public var headers: [String: String] {
-        var baseHeaders: [String: String] = [:]
         switch self {
-        case .checkVersion, .fetchBanner, .fetchClubCategories, .fetchHotClubs: return [:]
-        case .uploadFiles:
-            baseHeaders["Content-Type"] = "multipart/form-data"
+        case .checkVersion, .fetchBanner, .fetchClubCategories, .fetchHotClubs, .uploadFiles:
+            return [:]
         }
-        return baseHeaders
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Koin/Data/Service/Network/API/CoreAPI.swift` around lines 42 - 49, The
headers getter currently sets "Content-Type" for the .uploadFiles case which can
interfere with AF.upload's boundary handling; remove the manual assignment in
the public var headers (specifically the .uploadFiles branch) so it returns
baseHeaders (empty or only other required headers) and allow Alamofire's
AF.upload(multipartFormData:...) to set the Content-Type with boundary
automatically; update any tests or callers that assumed a preset Content-Type in
headers for uploadFiles.
Koin/Presentation/LostItem/PostLostItem/PostLostItemViewModel.swift (1)

35-35: Consider dependency injection for better testability.

The inline instantiation of DefaultUploadFileUseCase(coreRepository: DefaultCoreRepository(service: DefaultCoreService())) makes this view model harder to test. Consider injecting the use case through the initializer, similar to how updateLostItemUseCase is handled in EditLostItemViewModel.

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

In `@Koin/Presentation/LostItem/PostLostItem/PostLostItemViewModel.swift` at line
35, The inline creation of uploadFileUseCase using
DefaultUploadFileUseCase(DefaultCoreRepository(DefaultCoreService())) hinders
testing; change PostLostItemViewModel to accept an UploadFileUseCase via its
initializer (similar to how updateLostItemUseCase is injected in
EditLostItemViewModel), add a stored property for uploadFileUseCase, and provide
a default initializer value that constructs
DefaultUploadFileUseCase(coreRepository: DefaultCoreRepository(service:
DefaultCoreService())) so existing callers are unaffected while tests can inject
mocks.
Koin/Domain/UseCase/Core/UploadFileUseCase.swift (1)

23-25: Prefer keeping UploadDomain typed beyond use case boundary.

Line 24 converts to String early, which makes downstream contracts stringly-typed. Consider passing UploadDomain through repository/service and mapping to rawValue only in CoreAPI request construction.

♻️ Suggested direction
- func uploadFiles(files: [Data], domain: String) -> AnyPublisher<FileUploadResponse, ErrorResponse>
+ func uploadFiles(files: [Data], domain: UploadDomain) -> AnyPublisher<FileUploadResponse, ErrorResponse>
- return coreRepository.uploadFiles(files: files, domain: domain.rawValue)
+ return coreRepository.uploadFiles(files: files, domain: domain)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Koin/Domain/UseCase/Core/UploadFileUseCase.swift` around lines 23 - 25, The
execute method is converting UploadDomain to String too early; update the use
case and downstream API to keep UploadDomain typed until the API layer by
changing the signature of UploadFileUseCase.execute(files:domain:) to accept
UploadDomain (not String) and adjust coreRepository.uploadFiles to accept an
UploadDomain parameter (or overload it), then perform domain.rawValue only
inside the CoreAPI request construction (e.g., in the class/method that builds
the HTTP request), leaving execute(files:domain:), UploadDomain, and
coreRepository.uploadFiles with the strong-typed enum across the use-case
boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Koin/Presentation/Chat/Chat/ChatViewModel.swift`:
- Around line 77-79: ChatViewModel currently hardcodes the upload domain to
.lostItem in uploadFiles, causing uploads from NoticeDataViewController and
ChatListTableViewController to hit the wrong backend; update ChatViewModel to
accept a domain parameter (similar to articleId/chatRoomId) or compute domain
from the originating context and use that when calling
uploadFileUseCase.execute(files:domain:), ensuring uploadFiles (and any
callers/initializers) pass the correct domain instead of .lostItem.

---

Nitpick comments:
In `@Koin/Data/Service/Network/API/CoreAPI.swift`:
- Around line 42-49: The headers getter currently sets "Content-Type" for the
.uploadFiles case which can interfere with AF.upload's boundary handling; remove
the manual assignment in the public var headers (specifically the .uploadFiles
branch) so it returns baseHeaders (empty or only other required headers) and
allow Alamofire's AF.upload(multipartFormData:...) to set the Content-Type with
boundary automatically; update any tests or callers that assumed a preset
Content-Type in headers for uploadFiles.

In `@Koin/Domain/UseCase/Core/UploadFileUseCase.swift`:
- Around line 23-25: The execute method is converting UploadDomain to String too
early; update the use case and downstream API to keep UploadDomain typed until
the API layer by changing the signature of
UploadFileUseCase.execute(files:domain:) to accept UploadDomain (not String) and
adjust coreRepository.uploadFiles to accept an UploadDomain parameter (or
overload it), then perform domain.rawValue only inside the CoreAPI request
construction (e.g., in the class/method that builds the HTTP request), leaving
execute(files:domain:), UploadDomain, and coreRepository.uploadFiles with the
strong-typed enum across the use-case boundary.

In `@Koin/Presentation/LostItem/EditLostItem/EditLostItemViewModel.swift`:
- Line 32: The field uploadFileUseCase is being instantiated inline which harms
testability; make it an injectable dependency by adding an initializer parameter
(e.g., uploadFileUseCase: UploadFileUseCase) to EditLostItemViewModel and assign
it to the uploadFileUseCase property instead of creating
DefaultUploadFileUseCase(DefaultCoreRepository(DefaultCoreService())) inside the
class; optionally provide a default parameter value that constructs
DefaultUploadFileUseCase(...) to preserve existing call sites while enabling
tests to inject a mock implementation.

In `@Koin/Presentation/LostItem/PostLostItem/PostLostItemViewModel.swift`:
- Line 35: The inline creation of uploadFileUseCase using
DefaultUploadFileUseCase(DefaultCoreRepository(DefaultCoreService())) hinders
testing; change PostLostItemViewModel to accept an UploadFileUseCase via its
initializer (similar to how updateLostItemUseCase is injected in
EditLostItemViewModel), add a stored property for uploadFileUseCase, and provide
a default initializer value that constructs
DefaultUploadFileUseCase(coreRepository: DefaultCoreRepository(service:
DefaultCoreService())) so existing callers are unaffected while tests can inject
mocks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a966ad0d-1af4-48d0-b3f7-ef92caf1145f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c6c057 and f9654a7.

📒 Files selected for processing (18)
  • Koin/Data/Repository/DefaultCoreRepository.swift
  • Koin/Data/Repository/DefaultShopRepository.swift
  • Koin/Data/Service/CoreService.swift
  • Koin/Data/Service/Network/API/CoreAPI.swift
  • Koin/Data/Service/Network/API/ShopAPI.swift
  • Koin/Data/Service/Network/NetworkService.swift
  • Koin/Data/Service/ShopService.swift
  • Koin/Domain/Model/UploadDomain.swift
  • Koin/Domain/Repository/CoreRepository.swift
  • Koin/Domain/Repository/ShopRepository.swift
  • Koin/Domain/UseCase/Core/UploadFileUseCase.swift
  • Koin/Domain/UseCase/Shop/UploadFileUseCase.swift
  • Koin/Presentation/Chat/Chat/ChatViewModel.swift
  • Koin/Presentation/LostItem/EditLostItem/EditLostItemViewModel.swift
  • Koin/Presentation/LostItem/PostLostItem/PostLostItemViewModel.swift
  • Koin/Presentation/Shop/ShopReview/ShopReviewViewModel.swift
  • Koin/Presentation/Shop/ShopReviewList/ReviewListViewController.swift
  • koin.xcodeproj/project.pbxproj
💤 Files with no reviewable changes (3)
  • Koin/Domain/Repository/ShopRepository.swift
  • Koin/Data/Repository/DefaultShopRepository.swift
  • Koin/Domain/UseCase/Shop/UploadFileUseCase.swift

Comment on lines 77 to 79
private func uploadFiles(files: [Data]) {
uploadFileUseCase.execute(files: files).sink { [weak self] completion in
uploadFileUseCase.execute(files: files, domain: .lostItem).sink { [weak self] completion in
if case let .failure(error) = completion {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UploadDomain enum values =="
fd 'UploadDomain.swift$' -x sed -n '1,140p' {}

echo
echo "== All execute(files:domain:) call sites =="
rg -n --type=swift -C2 'uploadFileUseCase\.execute\(files:\s*[^,]+,\s*domain:\s*\.[A-Za-z]+' Koin

echo
echo "== Chat-related viewmodels using upload =="
rg -n --type=swift -C3 'final class .*ChatViewModel|case uploadFile\(\[Data\]\)|sendMessage\(message:.*isImage' Koin

Repository: BCSDLab/KOIN_iOS

Length of output: 8132


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ChatViewModel instantiation points =="
rg -n --type=swift 'ChatViewModel\(' Koin | head -20

echo
echo "== Views/ViewControllers that use ChatViewModel =="
rg -n --type=swift '@StateObject.*ChatViewModel|@ObservedObject.*ChatViewModel|viewModel.*ChatViewModel|ChatViewModel()' Koin | head -20

echo
echo "== Chat module structure =="
fd 'Chat' Koin/Presentation -type d

echo
echo "== CallVan chat domain usage =="
rg -n --type=swift 'callVanChat' Koin

echo
echo "== Full ChatViewModel input/transform to see domain parameter flow =="
sed -n '1,130p' Koin/Presentation/Chat/Chat/ChatViewModel.swift

Repository: BCSDLab/KOIN_iOS

Length of output: 2195


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ChatViewModel init signature and full source =="
sed -n '1,50p' Koin/Presentation/Chat/Chat/ChatViewModel.swift

echo
echo "== LostItemDataViewController ChatViewModel instantiation context =="
sed -n '270,280p' Koin/Presentation/LostItem/LostItemData/LostItemDataViewController.swift

echo
echo "== NoticeDataViewController ChatViewModel instantiation context =="
sed -n '285,295p' Koin/Presentation/Notice/NoticeData/NoticeDataViewController.swift

echo
echo "== Check if any viewModel receives domain parameter =="
rg -n --type=swift 'domain.*:' Koin/Presentation/Chat/Chat/ChatViewModel.swift

echo
echo "== Look for article/request types to infer domain =="
rg -n --type=swift 'articleId|chatRoomId' Koin/Presentation/Chat/Chat/ChatViewModel.swift | head -10

Repository: BCSDLab/KOIN_iOS

Length of output: 4082


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== callVanChat and callVanReport domain usage =="
rg -n --type=swift 'callVanChat|callVanReport' Koin

echo
echo "== Backend domain path construction (if exposed in client) =="
rg -n --type=swift 'domain.*upload|upload.*domain' Koin | head -10

echo
echo "== UploadFileUseCase to understand domain usage =="
fd 'UploadFileUseCase.swift' -o 'DefaultUploadFileUseCase.swift' -x cat {}

Repository: BCSDLab/KOIN_iOS

Length of output: 2019


ChatViewModel hardcodes .lostItem upload domain for all chat contexts.

Line 78 always routes uploads to /LOST_ITEMS/upload/files regardless of origin. ChatViewModel is instantiated from three distinct features:

  • LostItemDataViewController (lost item context — correct)
  • NoticeDataViewController (notice context — incorrect)
  • ChatListTableViewController (generic chat context — incorrect)

Uploads from Notice and ChatList contexts will be routed to the wrong backend domain. Consider passing domain as an init parameter (similar to how articleId and chatRoomId are passed) or determining it from context at upload time.

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

In `@Koin/Presentation/Chat/Chat/ChatViewModel.swift` around lines 77 - 79,
ChatViewModel currently hardcodes the upload domain to .lostItem in uploadFiles,
causing uploads from NoticeDataViewController and ChatListTableViewController to
hit the wrong backend; update ChatViewModel to accept a domain parameter
(similar to articleId/chatRoomId) or compute domain from the originating context
and use that when calling uploadFileUseCase.execute(files:domain:), ensuring
uploadFiles (and any callers/initializers) pass the correct domain instead of
.lostItem.

@hgjwilly-koreatech hgjwilly-koreatech merged commit 2d7bb1b into develop Mar 14, 2026
1 check passed
@hgjwilly-koreatech hgjwilly-koreatech deleted the refactor/uploadFileUsecase branch March 14, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

REFACTOR 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant