Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6242bf6
refactor: ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๋ ˆ์ด์•„์›ƒ๊ณผ ๋ฒ„ํŠผ ์ƒํ˜ธ์ž‘์šฉ ๊ฐœ์„ 
Roy-wonji Mar 25, 2026
22b01ad
feat: add history API domain ๊ตฌํ˜„ #12
Roy-wonji Mar 26, 2026
aa244ad
feat: add History data ๋ชจ๋ธ ๊ตฌํ˜„ ๋ฐ ํ”„๋กœํ•„ ์กฐํšŒ ๋ชจ๋ธ ์ˆ˜์ • #12
Roy-wonji Mar 26, 2026
baa54bd
feat: add History repository ๊ตฌํ˜„ ๋ฐ Profile ์ˆ˜์ • ๊ธฐ๋Šฅ ๊ฐœ์„  #12
Roy-wonji Mar 26, 2026
4192eba
feat: add History service layer ๊ตฌํ˜„ ๋ฐ Profile ์š”์ฒญ ๋ชจ๋ธ ์ˆ˜์ • #12
Roy-wonji Mar 26, 2026
12331e9
feat: add History repository interface ๋ฐ Profile ์ˆ˜์ • #12
Roy-wonji Mar 26, 2026
5a5c86b
feat: History ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ ๋ฐ Profile ํ†ต๊ณ„ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ #12
Roy-wonji Mar 26, 2026
e4d15ff
feat: add HistoryUseCaseImpl ๋ฐ Profile ์ˆ˜์ • #12
Roy-wonji Mar 26, 2026
cdf1b47
feat: add TrainStation ์—ญ ์„ ํƒ ๊ธฐ๋Šฅ ๋ฐ LocationManager ๋ฆฌํŒฉํ† ๋ง #11
Roy-wonji Mar 26, 2026
d9436a5
feat: async/await ์ง€์› LocationPermissionManager ์ถ”๊ฐ€ #10
Roy-wonji Mar 26, 2026
792f348
feat: ์—ญ ์„ ํƒ ๋ฐ ํƒ์ƒ‰ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ #11
Roy-wonji Mar 26, 2026
d905cf6
feat: Profile ํžˆ์Šคํ† ๋ฆฌ ๋ฐ์ดํ„ฐ ์—ฐ๋™ ๋ฐ UI ๊ฐœ์„  #12
Roy-wonji Mar 26, 2026
12c6efd
feat: ์—ฌํ–‰ ์—ญ ์ •๋ณด ์ถ”์  ๋ฐ UI ๊ฐœ์„  #12
Roy-wonji Mar 26, 2026
1fab950
feat: Station API ๋ฐ ์ฆ๊ฒจ์ฐพ๊ธฐ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ #12
Roy-wonji Mar 26, 2026
dd8118c
feat: Station DTO ๋ชจ๋ธ ๋ฐ ๋งคํผ ์ถ”๊ฐ€ #12
Roy-wonji Mar 26, 2026
a7bdff1
feat: ์—ญ API ์„œ๋น„์Šค ๋ฐ ์š”์ฒญ ๋ชจ๋ธ ์ถ”๊ฐ€ #12
Roy-wonji Mar 26, 2026
bfcb446
feat: StationRepository ๊ธฐ๋Šฅ ๊ตฌํ˜„ #12
Roy-wonji Mar 26, 2026
a043db3
feat: Station ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ ์ถ”๊ฐ€ ๋ฐ Station enum ํ™•์žฅ #12
Roy-wonji Mar 26, 2026
aa4306f
feat: Station ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด ๊ตฌํ˜„ ๋ฐ UseCase ์ถ”๊ฐ€ #12
Roy-wonji Mar 26, 2026
ca56518
feat: TrainStation UI์™€ Station API ํ†ตํ•ฉ ๋ฐ ์ฆ๊ฒจ์ฐพ๊ธฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„ #12
Roy-wonji Mar 26, 2026
94d4f4e
feat: Station DI ๋“ฑ๋ก ์ถ”๊ฐ€ #12
Roy-wonji Mar 26, 2026
16b59fa
fix: Station API ์—”๋“œํฌ์ธํŠธ ์ˆ˜์ • ๋ฐ ์ฆ๊ฒจ์ฐพ๊ธฐ ๋กœ์ง ๊ฐœ์„  #12
Roy-wonji Mar 26, 2026
c7672e8
refactor: improve station API architecture and UX
Roy-wonji Mar 26, 2026
465ed69
refactor: api ์—์„œ ์กธ์•„์š” ์—ญ๋ฆฌ์ŠคํŠธ ์กฐํšŒ api ์ œ๊ฑฐ ๋ฐ ํ™ˆ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ ui ๊ตฌํ˜„ #11
Roy-wonji Mar 26, 2026
55c6954
๐Ÿš€ fix: ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๊ธฐ๋ฐ˜ ์ฃผ์š” ๊ฐœ์„ ์‚ฌํ•ญ ์ ์šฉ
Roy-wonji Mar 26, 2026
6bcfbf7
๐Ÿ—๏ธ refactor: TCA ํŒจํ„ด์— ๋งž๋Š” ์ƒ์ˆ˜ ๊ตฌ์กฐ ๊ฐœ์„ 
Roy-wonji Mar 26, 2026
64217a8
fix: ExploreView ์นดํ…Œ๊ณ ๋ฆฌ ์Šคํฌ๋กค ๋™์ž‘ ๊ฐœ์„ 
Roy-wonji Mar 26, 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
8 changes: 6 additions & 2 deletions Projects/App/Sources/Di/DiRegister.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@ public final class AppDIManager {
.register { AppleLoginRepositoryImpl() as AppleAuthRequestInterface }
.register { AppleOAuthRepositoryImpl() as AppleOAuthInterface }
.register { AppleOAuthProvider() as AppleOAuthProviderInterface }
// MARK: - ํšŒ์›๊ฐ€์ž…
// MARK: - ํšŒ์›๊ฐ€์ž…
.register { SignUpRepositoryImpl() as SignUpInterface }
// MARK: - ํ”„๋กœํ•„
// MARK: - ํ”„๋กœํ•„
.register(ProfileInterface.self) { ProfileRepositoryImpl() }
// MARK: - ํžˆ์Šคํ† ๋ฆฌ
.register(HistoryInterface.self) { HistoryRepositoryImpl() }
// MARK: - ์—ญ
.register(StationInterface.self) { StationRepositoryImpl() }



Expand Down
3 changes: 3 additions & 0 deletions Projects/App/Sources/Reducer/AppReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ extension AppReducer {
case .auth(.navigation(.presentMain)):
return .send(.view(.presentRoot))

case .home(.router(.routeAction(id: _, action: .home(.delegate(.presentAuth))))):
return .send(.view(.presentAuth))

case .home(.router(.routeAction(id: _, action: .profile(.navigation(.presentAuth))))):
return .send(.view(.presentAuth))

Expand Down
6 changes: 6 additions & 0 deletions Projects/Data/API/Sources/API/Domain/TimeSpotDomain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum TimeSpotDomain {
case auth
case place
case profile
case history
case station
}

extension TimeSpotDomain: DomainType {
Expand All @@ -28,6 +30,10 @@ extension TimeSpotDomain: DomainType {
return "api/v1/place"
case .profile:
return "api/v1/users"
case .history:
return "api/v1/histories"
case .station:
return "api/v1/stations"
}
}
}
19 changes: 19 additions & 0 deletions Projects/Data/API/Sources/API/History/HistoryAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// HistoryAPI.swift
// API
//
// Created by Wonji Suh on 3/26/26.
//
import Foundation

public enum HistoryAPI: String, CaseIterable {
case myHistory

public var description: String {
switch self {
case .myHistory:
return ""
}
}
}

25 changes: 25 additions & 0 deletions Projects/Data/API/Sources/API/Station/StationAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// StationAPI.swift
// API
//
// Created by Wonji Suh on 3/26/26.
//

import Foundation

public enum StationAPI {
case allStation
case addFavoriteStation
case deleteFavoriteStation(deleteStationId: Int)

public var description: String {
switch self {
case .allStation:
return ""
case .addFavoriteStation:
return "/favorites"
case .deleteFavoriteStation(let deleteStationId):
return "/favorites/\(deleteStationId)"
}
}
}
134 changes: 134 additions & 0 deletions Projects/Data/Model/Sources/History/DTO/HistoryDTOModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//
// HistoryDTOModel.swift
// Model
//
// Created by Wonji Suh on 3/26/26.
//

import Foundation

public typealias HistoryDTOModel = BaseResponseDTO<HistoryPageResponseDTO>

public struct HistoryPageResponseDTO: Decodable, Equatable {
public let content: [HistoryItemResponseDTO]
public let totalElements: Int
public let totalPages: Int
public let size: Int
public let number: Int
public let first: Bool
public let last: Bool

enum CodingKeys: String, CodingKey {
case content
case totalElements
case totalPages
case size
case number
case first
case last
case hasNext
}

public init(
content: [HistoryItemResponseDTO],
totalElements: Int,
totalPages: Int,
size: Int,
number: Int,
first: Bool,
last: Bool
) {
self.content = content
self.totalElements = totalElements
self.totalPages = totalPages
self.size = size
self.number = number
self.first = first
self.last = last
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let content = try container.decode([HistoryItemResponseDTO].self, forKey: .content)
let totalElements = try container.decode(Int.self, forKey: .totalElements)
let totalPages = try container.decode(Int.self, forKey: .totalPages)
let size = try container.decode(Int.self, forKey: .size)
let number = try container.decode(Int.self, forKey: .number)

let hasNext = try container.decodeIfPresent(Bool.self, forKey: .hasNext) ?? false
let first = try container.decodeIfPresent(Bool.self, forKey: .first) ?? (number == 0)
let last = try container.decodeIfPresent(Bool.self, forKey: .last) ?? !hasNext

self.init(
content: content,
totalElements: totalElements,
totalPages: totalPages,
size: size,
number: number,
first: first,
last: last
)
}
}

public struct HistoryItemResponseDTO: Decodable, Equatable {
public let visitingHistoryID: Int
public let stationID: Int
public let stationName: String
public let placeID: Int
public let placeName: String
public let placeCategory: String
public let startTime: String
public let endTime: String?
public let trainDepartureTime: String
public let totalDurationMinutes: Int
public let isInProgress: Bool
public let isSuccess: Bool
public let createdAt: String

enum CodingKeys: String, CodingKey {
case visitingHistoryID = "visitingHistoryId"
case stationID = "stationId"
case stationName
case placeID = "placeId"
case placeName
case placeCategory
case startTime
case endTime
case trainDepartureTime
case totalDurationMinutes
case isInProgress
case isSuccess
case createdAt
}

public init(
visitingHistoryID: Int,
stationID: Int,
stationName: String,
placeID: Int,
placeName: String,
placeCategory: String,
startTime: String,
endTime: String?,
trainDepartureTime: String,
totalDurationMinutes: Int,
isInProgress: Bool,
isSuccess: Bool,
createdAt: String
) {
self.visitingHistoryID = visitingHistoryID
self.stationID = stationID
self.stationName = stationName
self.placeID = placeID
self.placeName = placeName
self.placeCategory = placeCategory
self.startTime = startTime
self.endTime = endTime
self.trainDepartureTime = trainDepartureTime
self.totalDurationMinutes = totalDurationMinutes
self.isInProgress = isInProgress
self.isSuccess = isSuccess
self.createdAt = createdAt
}
}
42 changes: 42 additions & 0 deletions Projects/Data/Model/Sources/History/Mapper/HistoryDTOModel+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// HistoryDTOModel+.swift
// Model
//
// Created by Wonji Suh on 3/26/26.
//

import Entity

public extension HistoryPageResponseDTO {
func toDomain() -> HistoryEntity {
HistoryEntity(
items: content.map { $0.toDomain() },
totalElements: totalElements,
totalPages: totalPages,
size: size,
page: number + 1,
isFirstPage: first,
isLastPage: last
)
}
}

public extension HistoryItemResponseDTO {
func toDomain() -> HistoryItemEntity {
HistoryItemEntity(
id: visitingHistoryID,
stationID: stationID,
stationName: stationName,
placeID: placeID,
placeName: placeName,
placeCategory: placeCategory,
startTime: startTime,
endTime: endTime,
trainDepartureTime: trainDepartureTime,
totalDurationMinutes: totalDurationMinutes,
isInProgress: isInProgress,
isSuccess: isSuccess,
createdAt: createdAt
)
}
}
8 changes: 7 additions & 1 deletion Projects/Data/Model/Sources/Profile/DTO/ProfileDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ public typealias ProfileDTOModel = BaseResponseDTO<ProfileResponseDTO>
public struct ProfileResponseDTO: Decodable, Equatable {
let userID, email, nickname, mapAPI: String
let role, providerType, createdAt: String
let totalVisitCount, totalJourneyMinutes: Int

enum CodingKeys: String, CodingKey {
case userID = "userId"
case email, nickname
case mapAPI = "mapApi"
case role, providerType, createdAt
case totalVisitCount, totalJourneyMinutes
}

public init(
Expand All @@ -28,7 +30,9 @@ public struct ProfileResponseDTO: Decodable, Equatable {
mapAPI: String,
role: String,
providerType: String,
createdAt: String
createdAt: String,
totalJourneyMinutes: Int,
totalVisitCount: Int
) {
self.userID = userID
self.email = email
Expand All @@ -37,5 +41,7 @@ public struct ProfileResponseDTO: Decodable, Equatable {
self.role = role
self.providerType = providerType
self.createdAt = createdAt
self.totalVisitCount = totalVisitCount
self.totalJourneyMinutes = totalJourneyMinutes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ public extension ProfileResponseDTO {
email: self.email,
nickname: self.nickname,
mapType: mapType,
provider: provider
provider: provider,
totalVisitCount: totalVisitCount,
totalJourneyMinutes: totalJourneyMinutes
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// FavoriteStationMutationDTOModel.swift
// Model
//
// Created by Wonji Suh on 3/26/26.
//

import Foundation

public struct FavoriteStationMutationDTOModel: Decodable, Equatable {
public let code: Int
public let message: String
public let data: EmptyResponseDTO?

public init(
code: Int,
message: String,
data: EmptyResponseDTO? = nil
) {
self.code = code
self.message = message
self.data = data
}

enum CodingKeys: String, CodingKey {
case code
case message
case data
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.code = try container.decode(Int.self, forKey: .code)
self.message = try container.decode(String.self, forKey: .message)
self.data = try container.decodeIfPresent(EmptyResponseDTO.self, forKey: .data)
}
}
Loading