Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b928577
add: #18 - icon ์ถ”๊ฐ€
ChoiAnYong Feb 10, 2026
6d87048
chore: #18 - ์˜์กด์„ฑ ์˜ค๋ฅ˜ ์ˆ˜์ •
ChoiAnYong Feb 10, 2026
f3f34fa
feat: #18 - ๊ตญ๊ฐ€ ์ฝ”๋“œ ๋ณ€ํ™˜ ๋กœ์ง ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
ffe834f
feat: #18 - reusableViewIdentifier ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
cb4270a
feat: #18 - cellIdentifier ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
6484a03
feat: #18 - Home๊ณผํ˜„ Network ๋กœ์ง ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
90fbc90
del: #18 - ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ ์‚ญ์ œ
ChoiAnYong Feb 10, 2026
24b21fe
fix: #18 - ๋ง ์ค„์ž„ํ‘œ ๋ฏธ์ ์šฉ ์˜ค๋ฅ˜ ์ˆ˜์ •
ChoiAnYong Feb 10, 2026
1cb99e7
feat: #18 - NetworkError ์ผ€์ด์Šค ์ถ”๊ฐ€
ChoiAnYong Feb 10, 2026
cdc04ff
design: #18 - ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ ํ™”๋ฉด ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
4a19bc2
design: #18 - CategoryChipCell ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
1b0f08a
design: #18 - PopularInfoCell ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
97cf0aa
feat: #18 - asyncThowsRequest ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
777b41e
design: #18 - TabItem ์ƒ‰์ƒ ์ ์šฉ
ChoiAnYong Feb 10, 2026
1a0e32e
feat: #18 - Home ๊ด€๋ จ Domain ๋กœ์ง ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
c2727fb
feat: #18 - Home ๊ด€ํ˜„ Data ๋กœ์ง ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
abba511
feat: #18 - HomePresentationModel ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
b5c9c32
design: #18 - HomeHeaderView ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
f981fb0
design: #18 - HomeFooterButtonView ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
9af5995
design: #18 - HomeBannerCell ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
46cc298
design: #18 - RecommendInfoCell ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
7670fc4
design: #18 - HomeBannerEmptyView ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
39d6928
design: #18 - HomeBannerOnGogingView ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
ee416c6
design: #18 - HomeBannerUpCommingView ๊ตฌํ˜„
ChoiAnYong Feb 10, 2026
c8a331b
feat: #18 - Home CompositionalLayout ์„ธํŒ…
ChoiAnYong Feb 10, 2026
19dd8aa
feat: #18 - feature ๋ชจ๋“ˆ ์ƒ์„ฑ
ChoiAnYong Feb 11, 2026
e5c1cfc
feat: #18 - Setting TableView ์„ธํŒ…
ChoiAnYong Feb 11, 2026
8498de8
design: #18 - SettingView ๊ตฌํ˜„
ChoiAnYong Feb 11, 2026
a32080b
feat: #18 - Setting RIBs ๊ตฌํ˜„
ChoiAnYong Feb 11, 2026
ca13615
refactor: #18 - ํ™”๋ฉด ์ „ํ™˜ ๋กœ์ง ๋ฆฌํŽ™ํ† ๋ง
ChoiAnYong Feb 11, 2026
1c31756
design: #18 - SearchView ๊ตฌํ˜„
ChoiAnYong Feb 11, 2026
d73fd16
feat: #18 - Search RIBS ๊ตฌํ˜„
ChoiAnYong Feb 11, 2026
b6324e8
refactor: #18 - ํ™”๋ฉด ์ „ํ™˜ ๋กœ์ง ๋ฆฌํŽ™ํ† ๋ง
ChoiAnYong Feb 11, 2026
9ad4d99
feat: #18 - PopularTravel CompositionalLayout ์„ธํŒ…
ChoiAnYong Feb 11, 2026
c3e3bcd
design: #18 - PopularTravelView ๊ตฌํ˜„
ChoiAnYong Feb 11, 2026
d4fd2bd
feat: #18 - PopularTravel RIBs ๊ตฌํ˜„
ChoiAnYong Feb 11, 2026
1e5c079
feat: #18 - ํ™”๋ฉด ์ „ํ™˜ ๋‹ด๋‹น ๋ชจ๋“ˆ ๊ตฌํ˜„
ChoiAnYong Feb 11, 2026
d80fbe8
refactor: #18 - Home ๋ฆฌํŽ™ํ† ๋ง
ChoiAnYong Feb 11, 2026
ccc4049
chore: #18 - ์ž„์‹œ accessToken
ChoiAnYong Feb 11, 2026
a305883
design: #18 - ์ƒ‰์ƒ ์ ์šฉ
ChoiAnYong Feb 11, 2026
657ea4f
chore: #18 - ์ž„์‹œ ์˜์กด์„ฑ ์ฃผ์ž…
ChoiAnYong Feb 11, 2026
8871919
fix: #18 - ์ฝ”๋“œ๋ฆฌ๋ทฐ ๋ฐ˜์˜
ChoiAnYong Feb 13, 2026
1140041
refactor: #18 - Network ์—๋Ÿฌ ๋งคํ•‘ ๋ฐ ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด ๋ณ€ํ™˜ ๋ฆฌํŽ™ํ† ๋ง
ChoiAnYong Feb 13, 2026
8dcfcfb
fix: #18 - ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ์ˆ˜์ •
ChoiAnYong Feb 13, 2026
7546a19
feat: #18 - upcoming api ์—ฐ๊ฒฐ
ChoiAnYong Feb 13, 2026
ea28b57
fix: #18 - ์ฝ”๋“œ๋ฆฌ๋ทฐ ๋ฐ˜์˜
ChoiAnYong Feb 13, 2026
fa1e35b
Merge branch 'develop' into design/#18-Home
ChoiAnYong Feb 13, 2026
7612920
fix: #18 - ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ์ˆ˜์ •
ChoiAnYong Feb 13, 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ import ProjectDescription

public extension TargetDependency {
struct Features {
public struct Main {}
public struct Home {}
public struct TabBar {}
public struct Follow {}
public struct Travel {}
public struct Search {}
public struct Setting {}
public struct PopularTravel {}
}

struct Modules {}
Expand Down Expand Up @@ -63,3 +67,27 @@ public extension TargetDependency.Features.Travel {

static let feature = TargetDependency.Features.project(name: "Feature", group: group)
}

public extension TargetDependency.Features.Search {
static let group = "Search"

static let feature = TargetDependency.Features.project(name: "Feature", group: group)
}

public extension TargetDependency.Features.Setting {
static let group = "Setting"

static let feature = TargetDependency.Features.project(name: "Feature", group: group)
}

public extension TargetDependency.Features.Main {
static let group = "Main"

static let feature = TargetDependency.Features.project(name: "Feature", group: group)
}

public extension TargetDependency.Features.PopularTravel {
static let group = "PopularTravel"

static let feature = TargetDependency.Features.project(name: "Feature", group: group)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public extension Target {
dependencies: [TargetDependency],
scripts: [TargetScript],
isStatic: Bool = false,
hasResources: Bool = true
hasResources: Bool = true,
settings: Settings? = nil
) -> Target {
return .target(
name: name,
Expand All @@ -61,7 +62,7 @@ public extension Target {
resources: hasResources ? ["Resources/**"] : nil,
scripts: scripts,
dependencies: dependencies,
settings: .frameworkSettings
settings: settings ?? .frameworkSettings
)
}
}
3 changes: 1 addition & 2 deletions Projects/App/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ let project = Project.makeModule(
scripts: [.swiftLint],
dependencies: [
.data,
.Modules.networks,
.Features.rootFeature,
.Features.rootFeature
],
settings: .appSettings()
)
Expand Down
7 changes: 6 additions & 1 deletion Projects/App/Sources/Application/AppComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@

import Data
import Domain
import RIBs
import RootFeature

import RIBs

final class AppComponent: Component<EmptyDependency>, RootDependency {
var homeUsecase: HomeUsecaseProtocol {
let homeRepository = HomeRepository(homeService: makeHomeService(tokenProvider: tokenProvider))
return HomeUsecase(repository: homeRepository)
}

var tokenProvider: TokenProviding {
shared { TokenRepositoryFactory.makeTokenProvider() }
Expand Down
34 changes: 34 additions & 0 deletions Projects/Core/Sources/Extensions/Foundation+/String+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// String+.swift
// Core
//
// Created by ์ตœ์•ˆ์šฉ on 1/31/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

public extension String {
func toFlag() -> String {
guard self.count == 2 else { return "๐Ÿณ๏ธ" }

let base: UInt32 = 127397
var flagString = ""

for uni in self.uppercased().unicodeScalars {
if let scalar = UnicodeScalar(base + uni.value) {
flagString.append(String(scalar))
} else {
return "๐Ÿณ๏ธ"
}
}
return flagString
}

func toKoreanCountryName() -> String {
guard self.count == 2 else { return "์•Œ ์ˆ˜ ์—†์Œ" }

let locale = Locale(identifier: "ko_KR")
return locale.localizedString(forRegionCode: self) ?? "์•Œ ์ˆ˜ ์—†์Œ"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// UICollectionReusableView+.swift
// Core
//
// Created by ์ตœ์•ˆ์šฉ on 2/3/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import UIKit

public extension UICollectionReusableView {
static var reusableViewIdentifier : String {
return String(describing: self)
}
Comment on lines +9 to +14
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

๋งค๋ฒˆ idenifier ์ถ”๊ฐ€๋กœ ๋งŒ๋“ค์–ด์คฌ๋Š”๋ฐ ์ด ๋ถ€๋ถ„ ์žˆ๋Š” ๊ฒƒ ์ข‹์Šต๋‹ˆ๋‹ค. (์ƒ๊ฐ๋งŒ ํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ ๐Ÿ‘ )

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// UICollectionViewCell+.swift
// Core
//
// Created by ์ตœ์•ˆ์šฉ on 1/30/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import UIKit

public extension UICollectionViewCell {
static var cellIdentifier : String {
return String(describing: self)
}
}
15 changes: 15 additions & 0 deletions Projects/Core/Sources/Extensions/UIKit+/UITableViewCell+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// UITableViewCell+.swift
// Core
//
// Created by ์ตœ์•ˆ์šฉ on 2/10/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import UIKit

public extension UITableViewCell {
static var cellIdentifier: String {
return String(describing: self)
}
}
3 changes: 2 additions & 1 deletion Projects/Data/Sources/Adapter/TokenProviderAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public final class TokenProviderAdapter: TokenProviding, @unchecked Sendable {
}

public func accessToken() -> String? {
tokenRepository.get(.accessToken)
// tokenRepository.get(.accessToken)
"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJiYmE3ODIwYS0wMDUzLTQxZDctODdhYi00Zjk2ZWM3ZDI1MTMiLCJpYXQiOjE3NzA5NjQzMDUsImV4cCI6MTc3MTA1MDcwNX0.Sn8wNhZ1Ac-ETZDsOiSMMHHaALJNXxNKrbN_-4xD5REcVa2tJ0NiafhTKlbIuYafL1Acd9dDMIHjx3H33c5w8w"
}
Comment on lines 20 to 23
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 11, 2026

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

ํ•˜๋“œ์ฝ”๋”ฉ๋œ JWT ํ† ํฐ์€ ๋ณด์•ˆ ์œ„ํ—˜์ž…๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ๋ชฉ์ ์ด๋ผ ํ•˜๋”๋ผ๋„, ์ปค๋ฐ‹๋œ JWT๋Š” Git ํžˆ์Šคํ† ๋ฆฌ์— ์˜๊ตฌ์ ์œผ๋กœ ๋‚จ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ํ† ํฐ์„ ์ฆ‰์‹œ ๋ฌดํšจํ™”(revoke)ํ•˜๊ณ , ์‹ค์ œ ํ† ํฐ ์กฐํšŒ ๋กœ์ง์„ ๋ณต์›ํ•ด ์ฃผ์„ธ์š”. ํ…Œ์ŠคํŠธ ์‹œ์—๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋‚˜ ๋ณ„๋„ ์„ค์ • ํŒŒ์ผ(.gitignore ์ฒ˜๋ฆฌ)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๐Ÿ”’ ์ œ์•ˆ: ์›๋ž˜ ๋กœ์ง ๋ณต์›
 public func accessToken() -> String? {
-//        tokenRepository.get(.accessToken)
-        "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJiYmE3ODIwYS0wMDUzLTQxZDctODdhYi00Zjk2ZWM3ZDI1MTMiLCJpYXQiOjE3NzA3OTUwMjYsImV4cCI6MTc3MDg4MTQyNn0.oNCkotV0uA-3kCtTwGhTUwA9fUUhuO85p1k3952oTfRaULOw2Ix3RpXq_ta82ynmUK7F3i8F0Jb1d4_Rl-zcgA"
+        tokenRepository.get(.accessToken)
 }
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public func accessToken() -> String? {
tokenRepository.get(.accessToken)
// tokenRepository.get(.accessToken)
"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJiYmE3ODIwYS0wMDUzLTQxZDctODdhYi00Zjk2ZWM3ZDI1MTMiLCJpYXQiOjE3NzA3OTUwMjYsImV4cCI6MTc3MDg4MTQyNn0.oNCkotV0uA-3kCtTwGhTUwA9fUUhuO85p1k3952oTfRaULOw2Ix3RpXq_ta82ynmUK7F3i8F0Jb1d4_Rl-zcgA"
}
public func accessToken() -> String? {
tokenRepository.get(.accessToken)
}
๐Ÿงฐ Tools
๐Ÿช› Gitleaks (8.30.0)

[high] 22-22: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

๐Ÿค– Prompt for AI Agents
In `@Projects/Data/Sources/Adapter/TokenProviderAdapter.swift` around lines 20 -
23, The accessToken() function currently returns a hardcoded JWT string; restore
the original token retrieval by calling the token repository (uncomment or
reimplement tokenRepository.get(.accessToken)) in
TokenProviderAdapter.accessToken(), remove the embedded token from source, and
ensure fallback/optional handling remains (return String?); for tests, switch to
reading the token from an environment variable or a gitignored config and update
any callers of accessToken() if needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

์ด๋ถ€๋ถ„์€ ์ฝ”๋“œ์— ์žˆ๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์ž„์‹œ๋กœ ํ–ˆ๋”๋ผ๋ฉด xcconfig์—์„œ ๊ฐ€์ ธ์™”์œผ๋ฉด ๋” ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@KimNahun

PR description image: none provided.
Comment image: none provided.

์˜๊ฒฌ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋ง์”€ ์ฃผ์‹  ๋Œ€๋กœ ์ฝ”๋“œ๋Š” ์ฆ‰์‹œ ์›๋ณตํ•˜๊ณ , ๋กœ์ปฌ ์ž„์‹œ ํ† ํฐ์ด ํ•„์š”ํ•˜๋ฉด xcconfig๋ฅผ ํ†ตํ•ด ์ฃผ์ž…ํ•˜๋Š” ํ๋ฆ„์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ์ฆ‰์‹œ ์กฐ์น˜ โ€” ํ•˜๋“œ์ฝ”๋”ฉ ์ œ๊ฑฐ(ํŒŒ์ผ: Projects/Data/Sources/Adapter/TokenProviderAdapter.swift)
  • Line ๊ทผ์ฒ˜: accessToken()
  • ๋ณ€๊ฒฝ์•ˆ:
public func accessToken() -> String? {
    tokenRepository.get(.accessToken)
}
  1. ์„ ํƒ ์‚ฌํ•ญ(๊ถŒ์žฅ) โ€” DEBUG ์ „์šฉ xcconfig fallback
  • ์šด์˜(Release)๊ณผ ํ…Œ์ŠคํŠธ/๊ฐœ๋ฐœ(Debug)์„ ๋ถ„๋ฆฌํ•ด, ์ €์žฅ์†Œ๋‚˜ ๋ฐ”์ด๋„ˆ๋ฆฌ์— ๋น„๋ฐ€๊ฐ’์„ ๋‚จ๊ธฐ์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • TokenRepository(๋˜๋Š” ํ˜„ Adapter)์—์„œ ์ €์žฅ์†Œ์— ํ† ํฐ์ด ์—†์„ ๋•Œ, DEBUG ๋นŒ๋“œ์— ํ•œํ•ด Info.plist ๊ฐ’์„ ์ฝ๋Š” fallback์„ ๋‘ก๋‹ˆ๋‹ค.

์˜ˆ์‹œ(Adapter์— DEBUG ์ „์šฉ fallback ์ถ”๊ฐ€):

public func accessToken() -> String? {
    if let token = tokenRepository.get(.accessToken) {
        return token
    }
    `#if` DEBUG
    if let token = Bundle.main.object(forInfoDictionaryKey: "ACCESS_TOKEN") as? String, !token.isEmpty {
        return token
    }
    `#endif`
    return nil
}

์„ค์ • ๋ฐฉ๋ฒ•

  • .gitignore์— ๋น„๋ฐ€ xcconfig๋ฅผ ๋ฌด์‹œ:
    • ์˜ˆ: Configurations/Debug.secrets.xcconfig, ํ˜น์€ **/secrets.xcconfig
  • Configurations/Debug.secrets.xcconfig(์ปค๋ฐ‹ ๊ธˆ์ง€, ๋กœ์ปฌ ์ „์šฉ):
    • ACCESS_TOKEN = your_dev_token_here
  • Configurations/Debug.xcconfig์— include:
    • #include? "Debug.secrets.xcconfig"
  • Target์˜ Debug Info.plist์— ํ‚ค ์ถ”๊ฐ€:
    • Key: ACCESS_TOKEN, Value: $(ACCESS_TOKEN) (Debug ์„ค์ •์—๋งŒ ์ ์šฉ)
  • Release๋Š” ์œ„ ํ‚ค๋ฅผ ์ •์˜ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋นˆ ๊ฐ’์œผ๋กœ ์œ ์ง€ํ•˜์—ฌ, ๋ฐฐํฌ๋ฌผ์— ๋น„๋ฐ€์ด ํฌํ•จ๋˜์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  1. ๋ณด์•ˆ ํ›„์† ์กฐ์น˜
  • ์ด๋ฏธ ์ปค๋ฐ‹๋œ JWT๋Š” ์ฆ‰์‹œ ์„œ๋ฒ„ ์ธก์—์„œ ํ๊ธฐ/๋กœํ…Œ์ด์…˜ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
  • (ํ•„์š” ์‹œ) PR ๋ธŒ๋žœ์น˜ ํžˆ์Šคํ† ๋ฆฌ์—์„œ ํ•ด๋‹น ํ† ํฐ ๋ฌธ์ž์—ด์„ ์ •๋ฆฌ(BFG/git filter-repo ๋“ฑ)ํ•˜๋Š” ๊ฒƒ๋„ ๊ถŒ์žฅ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

๋ฐ”๋กœ ์›๋ณต(1๋ฒˆ)๋งŒ ์ด PR์— ๋ฐ˜์˜ํ•˜๊ณ , DEBUG fallback(2๋ฒˆ)์€ ์›ํ•˜์‹œ๋ฉด ์ด๋ฒˆ PR์— ํฌํ•จํ•˜๊ฑฐ๋‚˜ ๋ณ„๋„ PR๋กœ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ• ์ง€ ์•Œ๋ ค์ฃผ์„ธ์š”!


โœ๏ธ Learnings added
Learnt from: KimNahun
Repo: YAPP-Github/27th-App-Team-1-iOS PR: 24
File: Projects/Data/Sources/Adapter/TokenProviderAdapter.swift:20-23
Timestamp: 2026-02-13T11:24:52.093Z
Learning: ํŒ€ ์„ ํ˜ธ: ์ž„์‹œ/๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ ๋น„๋ฐ€๊ฐ’์€ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉํ•˜์ง€ ์•Š๊ณ , gitignore๋œ xcconfig๋ฅผ ํ†ตํ•ด Info.plist๋กœ ์ฃผ์ž…ํ•œ ๋’ค DEBUG์—์„œ๋งŒ ์ฝ๋Š”๋‹ค.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

}
17 changes: 17 additions & 0 deletions Projects/Data/Sources/DI/HomeServiceFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// HomeServiceFactory.swift
// Data
//
// Created by ์ตœ์•ˆ์šฉ on 2/10/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Domain
import Networks

import Moya

public func makeHomeService(tokenProvider: TokenProviding) -> HomeServiceProtocol {
let provider: MoyaProvider<HomeAPI> = NetworkProviderFactory.makeAuthenticatedProvider(tokenProvider: tokenProvider)
return HomeService(provider: provider)
}
52 changes: 52 additions & 0 deletions Projects/Data/Sources/Repository/Home/HomeRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// HomeRepository.swift
// Data
//
// Created by ์ตœ์•ˆ์šฉ on 2/4/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

import Domain
import Networks

public final class HomeRepository: HomeRepositoryInterface {
private let homeService: HomeServiceProtocol

public init(homeService: HomeServiceProtocol) {
self.homeService = homeService
}

public func fetchMyTripInfo() async throws -> MyTripSummary {
do {
return try await homeService.getUpcoming().toDomain()
} catch {
throw error.toNDGLError()
}
}

public func fetchCategoryList() async throws -> [TripCategory] {
do {
return try await homeService.getCategoryList().map { $0.toDomain() }
} catch {
throw error.toNDGLError()
}
}

public func fetchPopularTripList(id: Int?, page: Int?, size: Int?) async throws -> [TripInfo] {
do {
return try await homeService.getPopularTripList(id: id, page: page, size: size).toDomain()
} catch {
throw error.toNDGLError()
}
}

public func fetchRecommendTripList(page: Int?, size: Int?) async throws -> [TripInfo] {
do {
return try await homeService.getRecommendTripList(page: page, size: size).toDomain()
} catch {
throw error.toNDGLError()
}
}
}
29 changes: 29 additions & 0 deletions Projects/Data/Sources/Transform/Error+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Error+.swift
// Data
//
// Created by ์ตœ์•ˆ์šฉ on 2/13/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

import Domain
import Networks

extension Error {
func toNDGLError() -> NDGLError {
if let networkError = self as? NetworkError {
switch networkError {
case .connectionFailed, .decodingFailed, .noData:
return .unknown("\(networkError.message)")
case .unknown(let string):
return .serverError(string)
case .serverError(let errorResponse):
return .serverError(errorResponse.message ?? "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.")
}
}

return .unknown("์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.")
}
}
18 changes: 18 additions & 0 deletions Projects/Data/Sources/Transform/ProgramResponse+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// ProgramResponse+.swift
// Data
//
// Created by ์ตœ์•ˆ์šฉ on 2/10/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

import Domain
import Networks

extension ProgramResponse {
func toDomain() -> TripCategory {
.init(id: self.id, creator: self.name, viedoType: VideoType(rawValue: self.type))
}
}
29 changes: 29 additions & 0 deletions Projects/Data/Sources/Transform/TripResponse+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// TripResponse+.swift
// Data
//
// Created by ์ตœ์•ˆ์šฉ on 2/10/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

import Domain
import Networks

extension TripResponse {
func toDomain() -> [TripInfo] {
self.content.map {
.init(
id: $0.travelId,
title: $0.title,
thumbnailUrl: $0.thumbnail ?? "",
creator: $0.programName,
country: $0.country,
city: $0.city,
nights: $0.nights,
days: $0.days
)
}
}
Comment on lines +14 to +28
Copy link
Copy Markdown

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:

# First, find the TripResponse model definition
find . -name "*.swift" -type f | xargs grep -l "struct TripResponse" | head -5

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 138


๐Ÿ Script executed:

# Search for countryCode and country-related fields in Swift files
rg -n "countryCode|country_code|countryIso" --type=swift -C 3

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 1146


๐Ÿ Script executed:

# Look at the TripResponse structure to understand the available fields
rg -n "struct TripResponse|class TripResponse" --type=swift -A 20 | head -100

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 1554


๐Ÿ Script executed:

# Search for country code conversion or mapping logic
rg -n "countryCode|country.*code|CountryCode|Country.*Code" --type=swift -B2 -A2 | head -80

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 844


๐Ÿ Script executed:

# Check if there are any utility functions for country code conversion
rg -n "func.*country|country.*func" --type=swift -i -B1 -A3 | head -60

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 544


๐Ÿ Script executed:

# Look for any test files related to TripResponse transformation
find . -name "*TripResponse*Test*" -o -name "*Test*TripResponse*" | head -5

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 57


๐Ÿ Script executed:

# Read the complete String+.swift file to see all country-related extensions
cat Projects/Core/Sources/Extensions/Foundation+/String+.swift

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 912


๐Ÿ Script executed:

# Search for any reverse conversion (name to code) or country code mapping
rg -n "country.*Code|countryCode.*=|country_code" --type=swift -B3 -A3

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 1146


๐Ÿ Script executed:

# Search for test files, mocks, or sample data that shows what the country field contains
find . -type f \( -name "*Mock*" -o -name "*Test*" -o -name "*Stub*" \) -exec grep -l "country\|TripResponse\|TripContent" {} \; | head -10

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 151


๐Ÿ Script executed:

# Look for JSON files or API response examples
find . -type f \( -name "*.json" -o -name "*.txt" \) -exec grep -l "country\|travelId\|programName" {} \; | head -10

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 57


๐Ÿ Script executed:

# Check the mock file to see what data it uses
cat Projects/Features/FollowFeature/Sources/Mock/MockFollowDetailService.swift | head -100

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 3961


๐Ÿ Script executed:

# Look for any API documentation, comments, or README that describes the response format
find . -type f \( -name "README*" -o -name "*.md" \) -exec grep -l "country\|API\|response" {} \; | head -10

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 57


๐Ÿ Script executed:

# Search for any comments near the TripResponse or TripContentResponse definition
rg -n "TripContentResponse|TripResponse" --type=swift -B5 -A30 | grep -A30 "struct TripContentResponse"

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 2506


countryCode์— ๊ตญ๊ฐ€ ์ด๋ฆ„์ด ์ง์ ‘ ๋งคํ•‘๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ISO ์ฝ”๋“œ๋กœ์˜ ๋ณ€ํ™˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Line 26์—์„œ countryCode: $0.country๋กœ ๋งคํ•‘ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์‹ค์ œ ๋ฐ์ดํ„ฐ์—๋Š” country ํ•„๋“œ์— ๊ตญ๊ฐ€๋ช…(์˜ˆ: "ํƒœ๊ตญ")์ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ String.toKoreanCountryName(), String.toFlag() ๋“ฑ์˜ ํ™•์žฅ ๋ฉ”์„œ๋“œ๋“ค์€ 2์ž๋ฆฌ ISO ์ฝ”๋“œ(์˜ˆ: "TH")๋ฅผ ๊ธฐ๋Œ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

countryCode ํ•„๋“œ์—๋Š” ISO 3166-1 alpha-2 ์ฝ”๋“œ๊ฐ€ ๋งคํ•‘๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ, ๊ตญ๊ฐ€๋ช…์„ ISO ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ DTO์— ์‹ค์ œ ์ฝ”๋“œ ํ•„๋“œ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๐Ÿค– Prompt for AI Agents
In `@Projects/Data/Sources/Transform/TripResponse`+.swift around lines 14 - 29, In
TripResponse.toDomain() the countryCode is incorrectly set to the country name
(countryCode: $0.country) which must be an ISO 3166-1 alpha-2 code; update the
mapping in TripResponse.toDomain() to convert the incoming country name to its
2-letter ISO code (or use the DTO's ISO field if available) by calling the
conversion helper (e.g., a new/existing countryNameToISO or similar extension)
before assigning to countryCode so downstream extensions like
String.toKoreanCountryName()/toFlag() receive the expected ISO code.

}
40 changes: 40 additions & 0 deletions Projects/Data/Sources/Transform/UpcomingResponse+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// UpcomingResponse+.swift
// Data
//
// Created by ์ตœ์•ˆ์šฉ on 2/13/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

import Domain
import Networks

extension UpcomingResponse {
func toDomain() -> MyTripSummary {
return .init(
id: self.userTravelId,
title: self.title,
startDay: self.startDate.toDate() ?? .now,
endDay: self.endDate.toDate() ?? .now,
Comment on lines +19 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

๋‚ ์งœ ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ .now ํด๋ฐฑ์€ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์šฉํžˆ ์ „ํŒŒํ•ฉ๋‹ˆ๋‹ค.

startDate ๋˜๋Š” endDate ํŒŒ์‹ฑ์ด ์‹คํŒจํ•˜๋ฉด ํ˜„์žฌ ๋‚ ์งœ๋กœ ๋Œ€์ฒด๋˜์–ด, HomeBannerCell์—์„œ D-day ๊ณ„์‚ฐ์ด ์ž˜๋ชป๋˜๊ฑฐ๋‚˜ ์—ฌํ–‰ ์ƒํƒœ(upcoming/ongoing/empty)๊ฐ€ ์˜คํŒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ตœ์†Œํ•œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ฑฐ๋‚˜, toDomain()์„ throws/Optional ๋ฐ˜ํ™˜์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ํ˜ธ์ถœ๋ถ€์—์„œ ์ ์ ˆํžˆ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๐Ÿค– Prompt for AI Agents
In `@Projects/Data/Sources/Transform/UpcomingResponse`+.swift around lines 19 -
20, The current use of startDate.toDate() ?? .now and endDate.toDate() ?? .now
silently masks parse failures and can miscompute D-day/state; update the
transformation so parse failures are propagated instead of defaulting to .now:
modify toDomain() (or the surrounding mapper) to return an Optional or be
throws, remove the .now fallbacks when assigning startDay/endDay (use guard/try
to exit/return nil or rethrow on parse failure), and/or add a clear
processLogger.error or similar when toDate() returns nil so callers can handle
invalid input; reference startDate, endDate, toDate(), and toDomain() when
making the change.

tripSchedule: .init(
id: self.upcomingUserTravelPlace.id,
day: 1, // ์„œ๋ฒ„์—์„œ ์ฒซ ์ผ์ •๋งŒ ๋ณด๋‚ด์ฃผ๊ณ  ์žˆ์Œ
placeName: self.upcomingUserTravelPlace.place.name,
thumbnailUrl: self.upcomingUserTravelPlace.place.thumbnail ?? "",
transport: self.upcomingUserTravelPlace.place.category,
estimatedDuration: self.upcomingUserTravelPlace.estimatedDuration
)
)
}
}

extension String {
func toDate() -> Date? {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
return formatter.date(from: self)
}
}
15 changes: 15 additions & 0 deletions Projects/Domain/Sources/Error/NDGLError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// NDGLError.swift
// Domain
//
// Created by ์ตœ์•ˆ์šฉ on 2/13/26.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

public enum NDGLError: Error {
case serverError(String)
case unknown(String)
case authenticationFailed
}
Loading