Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e3356d7
[#57] Cloth 관련 이미지 API 수정
Hrepay Jan 31, 2026
b802ab2
[#57] 내정보 Email 필드 추가
Hrepay Jan 31, 2026
b9a544d
[#57] 프로젝트 아키텍처 md 파일 생성
Hrepay Jan 31, 2026
9b70ed6
[#57] 로그아웃 API 연결 및 화면 전환 완료
Hrepay Jan 31, 2026
9c410ac
[#57] CommentView UI 및 API 연결 수정
Hrepay Jan 31, 2026
23b70c6
[#57] isMine이 true일 때만 더보기 버튼 활성화
Hrepay Jan 31, 2026
cca4af3
[#57] 일별 기록 조회 isMine 연결 완료
Hrepay Jan 31, 2026
68c2dfe
[#57] 마이페이지 캘린더에 월별 기록 API 연결
Hrepay Jan 31, 2026
b8cb620
[#57] 마이페이지 달력조회 API 연결
Hrepay Jan 31, 2026
825543c
[#57] 달력의 날짜셀 UI 수정
Hrepay Jan 31, 2026
4d8426a
[#57] 설정 아이콘 수정
Hrepay Feb 1, 2026
9a40650
[#57] 설정 리스트 화면 연결
Hrepay Feb 1, 2026
e702047
[#57] 좋아요 한 기록 API 연결
Hrepay Feb 1, 2026
95ba15e
[#57] 좋아요 한 기록에서 FeedDetailView로 연결 완료
Hrepay Feb 1, 2026
f295d24
[#57] 내가 남긴 댓글 API 연결완료
Hrepay Feb 1, 2026
c116545
[#57] 내가 남긴 댓글에서 기록으로 바로 연결 완료
Hrepay Feb 1, 2026
62ad572
[#57] 아키텍처 구조에 맞게 수정
Hrepay Feb 1, 2026
99b2474
[#57] 차단한 계정 UI 및 API 연결
Hrepay Feb 1, 2026
0f9c714
[#57] 경고 1차 수정
Hrepay Feb 1, 2026
fc082f9
[#57] 회원 탈퇴 API 연결
Hrepay Feb 1, 2026
59c6894
[#57] 회원 탈퇴 구조 개선
Hrepay Feb 3, 2026
73d45fe
[#57] 탈퇴 플로우 UX 개선
Hrepay Feb 3, 2026
76ad5a6
[#57] 설정 리스트 여백도 터치 반응하게 수정
Hrepay Feb 3, 2026
67d56f6
[#57] 좋아요한 한 기록 좋아요 버튼 활성화
Hrepay Feb 3, 2026
fed1687
[#57] 설정에서 피드로 이동하는 플로우 추가
Hrepay Feb 3, 2026
d7604d0
[#57] 코드 리뷰 피드백 반영: DI 싱글톤화, 중복 코드 제거, 디버깅 로그 정리
Hrepay Feb 3, 2026
4663217
[#57] authDIContainer 호출 프로퍼티 변경
Hrepay Feb 3, 2026
f9f5d4c
[#57] 내 댓글 조회 API 수정
Hrepay Feb 3, 2026
f4a1ea0
Merge branch 'develop' into feat/#57
Hrepay Feb 3, 2026
5c89b70
[#57] SearchAPIService 정리 완료
Hrepay Feb 4, 2026
d66c321
[#57] CLAUDE.md 생성
Hrepay Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Architecture Documentation

이 문서는 **Codive-iOS** 프로젝트의 아키텍처 및 디자인 패턴에 대한 개요를 제공합니다.
이 프로젝트는 **SwiftUI**를 기반으로 하며, **MVVM (Model-View-ViewModel)** 패턴과 **Clean Architecture** 원칙을 따르고 있습니다. 또한 **DI Container**를 통한 의존성 주입과 **Router** 패턴을 통한 화면 전환 처리를 적용하여 모듈 간의 결합도를 낮추고 유지보수성을 높였습니다.

---

## 🏗 Architectural Overview

전체적인 구조는 **Clean Architecture**의 계층화된 접근 방식을 따르며, 데이터의 흐름은 단방향으로 관리됩니다.

### Core Principles
1. **관심사의 분리 (Separation of Concerns):** 각 레이어는 명확한 역할을 가지며 서로 독립적으로 동작합니다.
2. **의존성 규칙 (Dependency Rule):** 의존성은 항상 **안쪽(Domain Layer)**을 향해야 합니다. Presentation이나 Data 레이어는 Domain 레이어를 알지만, Domain 레이어는 외부 레이어를 알지 못합니다.
3. **테스트 용이성 (Testability):** 비즈니스 로직(Domain)은 UI나 프레임워크와 분리되어 있어 독립적으로 테스트가 가능합니다.

---

## 📂 Layer Structure (계층 구조)

각 Feature(기능)는 다음과 같은 3개의 주요 레이어로 구성됩니다.

### 1. Domain Layer (Inner Circle)
가장 안쪽에 위치하며, 비즈니스 로직을 담당합니다. 외부 라이브러리나 UI 프레임워크(SwiftUI, UIKit 등)에 의존하지 않는 순수 Swift 코드로 작성됩니다.
* **Entities:** 앱의 핵심 데이터 모델.
* **UseCases:** 비즈니스 로직을 실행하는 단위. Repository Interface를 사용하여 데이터를 요청합니다.
* **Interfaces (Repository Protocols):** Data Layer에서 구현해야 할 Repository의 추상화된 정의.

### 2. Data Layer (Outer Circle)
실제 데이터 처리를 담당합니다. API 통신, 로컬 DB 접근 등을 수행하며 Domain Layer의 Repository Interface를 구현합니다.
* **Repositories (Implementation):** Domain Layer의 Repository Interface를 실제로 구현한 클래스.
* **DataSources:** Remote(API) 또는 Local(DB, UserDefaults) 데이터 소스.
* **DTOs (Data Transfer Objects):** API 응답 모델 (Domain Entity로 매핑되어 사용됨).

### 3. Presentation Layer (Outer Circle)
사용자에게 데이터를 보여주고 입력을 받는 UI 계층입니다.
* **Views (SwiftUI):** UI를 구성하고 사용자의 입력을 받습니다. 비즈니스 로직을 직접 처리하지 않고 ViewModel에 위임합니다.
* **ViewModels:** View의 상태(State)를 관리하고, UseCase를 실행하여 데이터를 처리합니다. `@Published` 속성을 통해 View와 바인딩됩니다.

---

## 🧩 Modularization (Folder Structure)

프로젝트는 기능(Feature) 단위로 그룹화되어 있으며, 각 기능 내부는 Clean Architecture 레이어로 나뉩니다.

```
Codive/
├── Application/ # 앱 진입점 및 초기 설정 (AppConfigurator, AppRootView)
├── Features/ # 기능별 모듈
│ ├── Auth/
│ │ ├── Domain/ # Entity, UseCase, Repository Interface
│ │ ├── Data/ # Repository Impl, DTO, API Service
│ │ └── Presentation/ # View, ViewModel
│ ├── Feed/
│ ├── Home/
│ └── ...
├── Shared/ # 공통 사용 모듈 (DesignSystem, Extensions, Network, Storage)
├── DIContainer/ # 의존성 주입 컨테이너
└── Router/ # 화면 전환 및 내비게이션 로직
```

---

## 🔌 Dependency Injection (DI)

의존성 주입은 **DIContainer** 패턴을 사용하여 중앙에서 관리합니다.
* **AppDIContainer:** 앱 전체의 최상위 컨테이너로, 각 Feature의 DIContainer를 생성하고 관리합니다.
* **FeatureDIContainer (e.g., AuthDIContainer):** 각 기능 모듈에 필요한 UseCase, Repository, ViewModel 등의 인스턴스를 생성하고 주입합니다.
* 이를 통해 객체 간의 결합도를 낮추고 테스트 시 Mock 객체 주입을 용이하게 합니다.

---

## 🚦 Navigation (Router Pattern)

화면 전환 로직은 View에서 분리되어 **Router**와 **ViewFactory**가 담당합니다.
* **Router:** 내비게이션 스택을 관리하고 화면 전환을 수행합니다 (`AppRouter`, `NavigationRouter`).
* **ViewFactory:** 특정 화면(View)을 생성할 때 필요한 의존성(ViewModel 등)을 조립하여 View를 반환합니다.
* View는 `Router`를 통해 "어디로 갈지"만 요청하며, 실제 "어떻게 화면을 띄울지"는 Router가 처리합니다.

---

## 🛠 Tech Stack & Tools

* **Language:** Swift 6.0+
* **UI Framework:** SwiftUI
* **Architecture:** MVVM + Clean Architecture
* **Build Tool:** Tuist
* **Networking:** CodiveAPI (swift-openapi-generator)
* **Reactive Programming:** Combine / Swift Concurrency (async/await)
181 changes: 181 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & Project Setup

**Build system:** Tuist v4.65.4

### Common Commands

```bash
# Generate Xcode project from Tuist configuration
tuist generate

# Build the app (development)
xcodebuild -workspace Codive.xcworkspace -scheme Codive -configuration Debug

# Run tests
xcodebuild -workspace Codive.xcworkspace -scheme CodiveTests -configuration Debug test

# Lint code (runs automatically on build)
swiftlint

# Development environment setup (Fastlane)
fastlane setup_development

# App Store environment setup
fastlane setup_appstore
```

## Architecture Overview

This project follows **MVVM (Model-View-ViewModel)** pattern with **Clean Architecture** principles. The codebase is organized into feature-based modules with clear separation of concerns across three layers.

### Layer Structure

**Domain Layer** (Pure business logic, no external dependencies)
- **Entities:** Core data models
- **UseCases:** Business logic operations
- **Repository Interfaces:** Protocols that Data layer implements

**Data Layer** (Data access and networking)
- **Repositories (Implementation):** Implement Domain layer repository protocols
- **DataSources:** Remote (API via CodiveAPI) and Local (UserDefaults, CoreData) data sources
- **DTOs:** API response models that map to Domain entities

**Presentation Layer** (UI and state management)
- **Views (SwiftUI):** UI components and screens
- **ViewModels:** State management using `@Published` properties, orchestrate UseCases

### Project Structure

```
Codive/
├── Application/ # App entry point and global configuration
├── Core/ # Shared utilities and resources
├── Features/ # Feature modules (Auth, Home, Feed, Profile, etc.)
│ └── [Feature]/
│ ├── Domain/ # Business logic
│ ├── Data/ # Data access
│ └── Presentation/ # UI
├── Shared/ # Common components (DesignSystem, Extensions, Data utilities)
├── DIContainer/ # Dependency injection containers
└── Router/ # Navigation and routing
```

## Dependency Injection

The project uses a **DIContainer pattern** with centralized dependency management:

- **AppDIContainer** (`Codive/DIContainer/AppDIContainer.swift`): Root container that creates and manages all feature containers
- **FeatureDIContainers** (e.g., `AuthDIContainer.swift`): Individual containers for each feature that assemble their UseCases, Repositories, and ViewModels

Key pattern: Feature containers are initialized lazily by `AppDIContainer` and passed to Features. When implementing new features, create a corresponding `[Feature]DIContainer.swift` file following existing examples.

## Reactive Programming

- **Primary:** Swift Concurrency (async/await) for networking and asynchronous operations
- **Secondary:** Combine framework for View bindings and state management
- ViewModels use `@Published` properties for SwiftUI state binding
- API calls use async/await (see HomeDatasource.swift for examples)

## Key Dependencies

- **CodiveAPI:** Swift OpenAPI generated client for backend communication
- **Moya:** Networking layer foundation (used by CodiveAPI)
- **Kingfisher:** Image caching and loading
- **KakaoSDK:** Kakao authentication integration

## Code Style & Quality

**SwiftLint configuration** (`/.swiftlint.yml`):
- Lint is run as a pre-build script automatically
- Key rules:
- No force casting (`as!`) or force try (`try!`)
- Use `.isEmpty` instead of `.count == 0`
- Trailing closures required for SwiftUI style
- Type body length: warning at 300 lines, error at 400 lines
- File length: warning at 400 lines, error at 600 lines
- Cyclomatic complexity: warning at 15, error at 25
- Type nesting max 2 levels, function nesting max 3 levels

Run `swiftlint` manually to check code before committing.

## Testing

- **Framework:** XCTest
- **Location:** `CodiveTests/Features/`
- **Run tests:** `xcodebuild -workspace Codive.xcworkspace -scheme CodiveTests test`
- Tests should follow the same feature structure as main app (Domain, Data, Presentation)

## Build Configuration

- **Deployment Target:** iOS 16.0+
- **Swift Version:** 6.0+
- **Configurations:** Debug (with Dev app variant) and Release
- **Development Team:** BBVZV8T99P
- **Code Signing:** Manual (uses match)

Debug build:
- App name: "Codive (Dev)"
- Bundle ID: `com.codive.app`
- Provisioning: `match Development com.codive.app`

Release build:
- App name: "Codive"
- Provisioning: `match AppStore com.codive.app`

## Environment & Secrets

- **Base URL:** Set via `BASE_URL` in xcconfig files (`Codive/Resources/Secrets/Debug.xcconfig` and `Release.xcconfig`)
- **Kakao SDK:** `KAKAO_APP_KEY` and `KAKAO_AUTH_URL` configured in Info.plist
- **Custom URL schemes:** `kakao[APP_KEY]` and `codive://`

## Networking

API communication uses `CodiveAPI` package (Swift OpenAPI generated):
- Remote data sources (e.g., `HomeDatasource.swift`) call API services
- Responses are converted from DTOs to Domain entities
- Use async/await for API calls
- Error handling should follow repository pattern (catch in repositories, surface as Result or throw)

## Navigation

- **Router Pattern:** `AppRouter` and feature-specific `NavigationRouter`s manage navigation stack
- **ViewFactory:** Creates Views with injected dependencies
- Views request navigation through Router without creating child ViewModels directly
- Routes are typically handled in ViewModel, which calls Router methods

## Git Workflow

Current branch: `feat/#57`
Main branch for PRs: `develop`

Follow feature branch naming: `feat/#[issue-number]`

## Common Development Tasks

**Adding a new feature:**
1. Create feature directory in `Codive/Features/[FeatureName]/` with Domain/Data/Presentation subdirectories
2. Implement Domain layer (Entities, UseCases, Repository Interfaces)
3. Implement Data layer (Repositories, DataSources, DTOs)
4. Implement Presentation layer (ViewModels, Views)
5. Create `[FeatureName]DIContainer.swift` in `Codive/DIContainer/`
6. Register the container in `AppDIContainer`
7. Add routing in `AppRouter` if needed

**Modifying API interaction:**
- Update DTOs in appropriate Data layer (in feature)
- Ensure domain entity mapping in repository implementation
- Add corresponding API call method in repository if needed

**Adding shared UI components:**
- Place in `Codive/Shared/DesignSystem/` following existing structure (Buttons, Sheets, Navigation, Alerts, UIHelpers, etc.)
- Use custom style modifiers (e.g., `.customCornerRadius()`, `.customShadow()`)
- Design system colors are in `Codive/Resources/Colors.xcassets`

**Updating build configuration:**
- Modify `Project.swift` for Tuist configuration
- Rebuild with `tuist generate` after changes
- Info.plist entries defined in Project.swift `infoPlist` section
14 changes: 6 additions & 8 deletions Codive/Application/AppRootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct AppRootView: View {

init(appDIContainer: AppDIContainer) {
self._appRouter = StateObject(wrappedValue: appDIContainer.appRouter)
self.authDIContainer = appDIContainer.makeAuthDIContainer()
self.authDIContainer = appDIContainer.authDIContainer
self.appDIContainer = appDIContainer
self._authRepository = State(wrappedValue: self.authDIContainer.authRepository)
}
Expand All @@ -35,11 +35,9 @@ struct AppRootView: View {
authDIContainer.makeAuthFlowView()

case .termsAgreement:
TermsAgreementView(
onComplete: {
appRouter.navigateToMain()
}
)
TermsAgreementView {
appRouter.navigateToMain()
}

case .main:
MainTabView(appDIContainer: appDIContainer)
Expand Down Expand Up @@ -75,8 +73,8 @@ struct AppRootView: View {
return
}

let accessToken = queryItems.first(where: { $0.name == "accessToken" })?.value
let refreshToken = queryItems.first(where: { $0.name == "refreshToken" })?.value
let accessToken = queryItems.first { $0.name == "accessToken" }?.value
let refreshToken = queryItems.first { $0.name == "refreshToken" }?.value

guard let unwrappedAccessToken = accessToken,
let unwrappedRefreshToken = refreshToken else {
Expand Down
3 changes: 3 additions & 0 deletions Codive/Core/Resources/TextLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ enum TextLiteral {
static let withdrawTitle = "계정 탈퇴"
static let withdrawNotice = "탈퇴 전 아래 내용을 확인해주세요"
static let withdrawButton = "계정 탈퇴하기"
static let withdrawButtonLoading = "탈퇴 중..."
static let withdrawConfirmTitle = "정말 탈퇴하시겠습니까?"
static let withdrawConfirmMessage = "한 번 탈퇴하면 계정과 모든 데이터는 복구할 수 없습니다."

// Liked Records
static let likedRecordsEmpty = "좋아요 한 기록이 없어요!"
Expand Down
17 changes: 9 additions & 8 deletions Codive/DIContainer/AppDIContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@ final class AppDIContainer {
lazy var appRouter = AppRouter()
lazy var navigationRouter = NavigationRouter()

init() {
// Router 간 의존성 설정
appRouter.setNavigationRouter(navigationRouter)
}

// MARK: - Domain DIContainers
lazy var sharedDIContainer = SharedDIContainer()
lazy var closetDIContainer = ClosetDIContainer(navigationRouter: navigationRouter)

lazy var profileDIContainer = ProfileDIContainer(navigationRouter: navigationRouter)
lazy var authDIContainer = AuthDIContainer(appRouter: appRouter, navigationRouter: navigationRouter)

// MARK: - Feature DIContainers
func makeAuthDIContainer() -> AuthDIContainer {
return AuthDIContainer(
appRouter: appRouter,
navigationRouter: navigationRouter
)
}

func makeAddDIContainer() -> AddDIContainer {
return AddDIContainer(
Expand All @@ -36,7 +37,7 @@ final class AppDIContainer {
}

func makeSettingDIContainer() -> SettingDIContainer {
return SettingDIContainer(appRouter: appRouter, navigationRouter: navigationRouter)
return SettingDIContainer(appRouter: appRouter, navigationRouter: navigationRouter, profileDIContainer: profileDIContainer, authDIContainer: authDIContainer)
}

func makeReportDIContainer() -> ReportDIContainer {
Expand Down
Loading