diff --git a/Koin/Apps/SceneDelegate.swift b/Koin/Apps/SceneDelegate.swift index 3620966c6..2fda49895 100644 --- a/Koin/Apps/SceneDelegate.swift +++ b/Koin/Apps/SceneDelegate.swift @@ -10,7 +10,16 @@ import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - var urlParameters: [String: String]? + private var isPresentingErrorViewController = false + + override init() { + super.init() + NotificationCenter.default.addObserver(self, selector: #selector(presentErrorViewController), name: NSNotification.Name("ServerError"), object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } func scene(_ scene: UIScene, willConnectTo session: UISceneSession, @@ -115,5 +124,33 @@ extension SceneDelegate { return viewController } - + @objc private func presentErrorViewController() { + + guard isPresentingErrorViewController == false else { + return + } + isPresentingErrorViewController = true + + if let navigationController = window?.rootViewController as? UINavigationController { + + let homeViewController = makeHomeViewController() + let completion: ()->Void = { [weak self] in + navigationController.setViewControllers([homeViewController], animated: false) + navigationController.dismiss(animated: true) { + self?.isPresentingErrorViewController = false + } + } + let errorViewController = ErrorViewController(completion: completion).then { + $0.modalPresentationStyle = .fullScreen + } + + if let _ = navigationController.presentedViewController { + navigationController.dismiss(animated: true) { + navigationController.present(errorViewController, animated: true) + } + } else { + navigationController.present(errorViewController, animated: true) + } + } + } } diff --git a/Koin/Core/Workers/KeychainWorker.swift b/Koin/Core/Workers/KeychainWorker.swift index 77fdb1698..940e196f8 100644 --- a/Koin/Core/Workers/KeychainWorker.swift +++ b/Koin/Core/Workers/KeychainWorker.swift @@ -19,58 +19,89 @@ final class KeychainWorker { } static let shared = KeychainWorker() - private init() { } + private init() {} + private var keychains: [TokenType: String?] = [:] + private let lock = NSLock() func create(key: TokenType, token: String) { + lock.lock() + defer { + lock.unlock() + } + let query: NSDictionary = [ kSecClass: kSecClassGenericPassword, kSecAttrAccount: keyType(key: key), kSecValueData: token.data(using: .utf8, allowLossyConversion: false) as Any ] SecItemDelete(query) + keychains.removeValue(forKey: key) let status = SecItemAdd(query, nil) - if status != errSecSuccess { - print("Failed to save token, status code: \(status)") + if status == errSecSuccess { + keychains.updateValue(token, forKey: key) + } else { + print("Failed to save \(key) token,", SecCopyErrorMessageString(status, nil) ?? "") } } func read(key: TokenType) -> String? { + lock.lock() + defer { + lock.unlock() + } + + if let token: String? = keychains[key] { + return token + } + let query: NSDictionary = [ kSecClass: kSecClassGenericPassword, kSecAttrAccount: keyType(key: key), kSecReturnData: kCFBooleanTrue as Any, kSecMatchLimit: kSecMatchLimitOne ] - var dataTypeRef: AnyObject? let status = SecItemCopyMatching(query, &dataTypeRef) - if status == errSecSuccess { - if let retrievedData: Data = dataTypeRef as? Data { - let value = String(data: retrievedData, encoding: String.Encoding.utf8) - return value - } else { return nil } + if status == errSecSuccess, + let retrievedData: Data = dataTypeRef as? Data, + let value = String(data: retrievedData, encoding: String.Encoding.utf8) { + keychains.updateValue(value, forKey: key) + return value + } else if status == errSecItemNotFound { + keychains.updateValue(nil, forKey: key) + return nil } else { - print("failed to loading, status code = \(status)") + print("Failed to load \(key) token,", SecCopyErrorMessageString(status, nil) ?? "") return nil } } func delete(key: TokenType) { + lock.lock() + defer { + lock.unlock() + } + + keychains.removeValue(forKey: key) + let query: NSDictionary = [ kSecClass: kSecClassGenericPassword, kSecAttrAccount: keyType(key: key) ] let status = SecItemDelete(query) - if status == errSecSuccess { - print("Item successfully deleted") - } else if status == errSecItemNotFound { - print("Item not found") - } else { - print("Error deleting the item, status code: \(status)") - } + if status == errSecSuccess { + return + } else if status == errSecItemNotFound { + print("\(key) token not found") + } else { + print("Error deleting \(key) token", SecCopyErrorMessageString(status, nil) ?? "") + } } +} + +extension KeychainWorker { private func keyType(key: TokenType) -> String { let keyType: String @@ -85,5 +116,4 @@ final class KeychainWorker { } return keyType } - } diff --git a/Koin/Data/DTOs/Decodable/ErrorResponse.swift b/Koin/Data/DTOs/Decodable/ErrorResponse.swift deleted file mode 100644 index 6138534cf..000000000 --- a/Koin/Data/DTOs/Decodable/ErrorResponse.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// ErrorResponse.swift -// koin -// -// Created by 김나훈 on 3/18/24. -// - -import Foundation - -struct ErrorResponse: Decodable, Error { - let code: String - let message: String -} diff --git a/Koin/Data/DTOs/Decodable/ErrorResponseDto.swift b/Koin/Data/DTOs/Decodable/ErrorResponseDto.swift new file mode 100644 index 000000000..fcf781aee --- /dev/null +++ b/Koin/Data/DTOs/Decodable/ErrorResponseDto.swift @@ -0,0 +1,19 @@ +// +// ErrorResponseDto.swift +// koin +// +// Created by 김나훈 on 3/18/24. +// + +import Foundation + +struct ErrorResponseDto: Decodable { + let code: String + let message: String +} + +extension ErrorResponseDto { + func toDomain(withStatusCode statusCode: Int) -> ErrorResponse { + return ErrorResponse(statusCode: statusCode, code: self.code, message: self.message) + } +} diff --git a/Koin/Data/Repository/DefaultAbTestRepository.swift b/Koin/Data/Repository/DefaultAbTestRepository.swift index c7dc8831d..707020259 100644 --- a/Koin/Data/Repository/DefaultAbTestRepository.swift +++ b/Koin/Data/Repository/DefaultAbTestRepository.swift @@ -16,6 +16,6 @@ final class DefaultAbTestRepository: AbTestRepository { } func assignAbTest(requestModel: AssignAbTestRequest) -> AnyPublisher { - return service.assignAbTest(requestModel: requestModel, retry: false) + return service.assignAbTest(requestModel: requestModel) } } diff --git a/Koin/Data/Repository/DefaultBusRepository.swift b/Koin/Data/Repository/DefaultBusRepository.swift index 2e5f8840a..dad512829 100644 --- a/Koin/Data/Repository/DefaultBusRepository.swift +++ b/Koin/Data/Repository/DefaultBusRepository.swift @@ -15,27 +15,27 @@ final class DefaultBusRepository: BusRepository { self.service = service } - func searchBusInformation(requestModel: SearchBusInfoRequest) -> AnyPublisher { + func searchBusInformation(requestModel: SearchBusInfoRequest) -> AnyPublisher { return service.searchBusInformation(requestModel: requestModel) } - func fetchShuttleRouteList() -> AnyPublisher { + func fetchShuttleRouteList() -> AnyPublisher { return service.fetchShuttleRouteList() } - func fetchExpressBusTimetableList(requestModel: FetchBusTimetableRequest) -> AnyPublisher { + func fetchExpressBusTimetableList(requestModel: FetchBusTimetableRequest) -> AnyPublisher { return service.fetchExpressTimetableList(requestModel: requestModel) } - func fetchCityBusTimetableList(requestModel: FetchCityBusTimetableRequest) -> AnyPublisher { + func fetchCityBusTimetableList(requestModel: FetchCityBusTimetableRequest) -> AnyPublisher { return service.fetchCityTimetableList(requestModel: requestModel) } - func fetchEmergencyNotice() -> AnyPublisher { + func fetchEmergencyNotice() -> AnyPublisher { return service.fetchEmergencyNotice() } - func fetchShuttleBusTimetable(id: String) -> AnyPublisher { + func fetchShuttleBusTimetable(id: String) -> AnyPublisher { service.fetchShuttleBusTimetable(id: id) .map { $0.toDomain() } .eraseToAnyPublisher() diff --git a/Koin/Data/Repository/DefaultCoreRepository.swift b/Koin/Data/Repository/DefaultCoreRepository.swift index d27239848..b1f958000 100644 --- a/Koin/Data/Repository/DefaultCoreRepository.swift +++ b/Koin/Data/Repository/DefaultCoreRepository.swift @@ -15,18 +15,18 @@ final class DefaultCoreRepository: CoreRepository { self.service = service } - func fetchVersion() -> AnyPublisher { + func fetchVersion() -> AnyPublisher { return service.fetchVersion() } - func fetBanner() -> AnyPublisher { + func fetBanner() -> AnyPublisher { return service.fetchBanner() } - func fetchClubCategories() -> AnyPublisher { + func fetchClubCategories() -> AnyPublisher { return service.fetchClubCategories() } - func fetchHotClubs() -> AnyPublisher { + func fetchHotClubs() -> AnyPublisher { return service.fetchHotClubs() } diff --git a/Koin/Data/Repository/DefaultDiningRepository.swift b/Koin/Data/Repository/DefaultDiningRepository.swift index e812776fa..221f0e6cd 100644 --- a/Koin/Data/Repository/DefaultDiningRepository.swift +++ b/Koin/Data/Repository/DefaultDiningRepository.swift @@ -17,10 +17,10 @@ final class DefaultDiningRepository: DiningRepository { } func fetchDiningList(requestModel: FetchDiningListRequest) -> AnyPublisher<[DiningDto], ErrorResponse> { - return diningService.fetchDiningList(requestModel: requestModel, retry: false) + return diningService.fetchDiningList(requestModel: requestModel) } - func fetchCoopShopList() -> AnyPublisher { + func fetchCoopShopList() -> AnyPublisher { return diningService.fetchCoopShopList() } diff --git a/Koin/Data/Repository/DefaultLandRepository.swift b/Koin/Data/Repository/DefaultLandRepository.swift index 9dd186179..262bebb24 100644 --- a/Koin/Data/Repository/DefaultLandRepository.swift +++ b/Koin/Data/Repository/DefaultLandRepository.swift @@ -15,11 +15,11 @@ final class DefaultLandRepository: LandRepository { self.service = service } - func fetchLandList() -> AnyPublisher { + func fetchLandList() -> AnyPublisher { return service.fetchLandList() } - func fetchLandDetail(requestModel: FetchLandDetailRequest) -> AnyPublisher { + func fetchLandDetail(requestModel: FetchLandDetailRequest) -> AnyPublisher { return service.fetchLandDetail(requestModel: requestModel) } diff --git a/Koin/Data/Repository/DefaultLostItemRepository.swift b/Koin/Data/Repository/DefaultLostItemRepository.swift index c28acf118..e3992ffc5 100644 --- a/Koin/Data/Repository/DefaultLostItemRepository.swift +++ b/Koin/Data/Repository/DefaultLostItemRepository.swift @@ -16,13 +16,13 @@ final class DefaultLostItemRepository: LostItemRepository { self.service = service } - func fetchLostItemList(requestModel: FetchLostItemListRequest) -> AnyPublisher { + func fetchLostItemList(requestModel: FetchLostItemListRequest) -> AnyPublisher { return service.fetchLostItemList(requestModel: requestModel) .map { $0.toDomain() } .eraseToAnyPublisher() } - func fetchLostItemData(id: Int) -> AnyPublisher { + func fetchLostItemData(id: Int) -> AnyPublisher { return service.fetchLostItemData(id: id) .map { $0.toDomain() } .eraseToAnyPublisher() @@ -32,7 +32,7 @@ final class DefaultLostItemRepository: LostItemRepository { return service.changeLostItemState(id: id) } - func deleteLostItem(id: Int) -> AnyPublisher { + func deleteLostItem(id: Int) -> AnyPublisher { return service.deleteLostItem(id: id) } @@ -42,7 +42,7 @@ final class DefaultLostItemRepository: LostItemRepository { .eraseToAnyPublisher() } - func fetchLostItemStats() -> AnyPublisher { + func fetchLostItemStats() -> AnyPublisher { return service.fetchLostItemStats() .map { $0.toDomain() } .eraseToAnyPublisher() diff --git a/Koin/Data/Repository/DefaultNoticeListRepository.swift b/Koin/Data/Repository/DefaultNoticeListRepository.swift index edf8ef2d7..d297c2820 100644 --- a/Koin/Data/Repository/DefaultNoticeListRepository.swift +++ b/Koin/Data/Repository/DefaultNoticeListRepository.swift @@ -15,8 +15,8 @@ final class DefaultNoticeListRepository: NoticeListRepository { self.service = service } - func fetchLostItemArticles(requestModel: FetchLostItemsRequest) -> AnyPublisher { - service.fetchLostItemArticles(requestModel: requestModel, retry: false) + func fetchLostItemArticles(requestModel: FetchLostItemsRequest) -> AnyPublisher { + service.fetchLostItemArticles(requestModel: requestModel) } @@ -33,27 +33,27 @@ final class DefaultNoticeListRepository: NoticeListRepository { } func fetchLostItem(id: Int) -> AnyPublisher { - service.fetchLostItem(id: id, retry: false) + service.fetchLostItem(id: id) } - func fetchNoticeArticles(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher { + func fetchNoticeArticles(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher { return service.fetchNoticeArticles(requestModel: requestModel) } - func fetchLostItemList(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher { + func fetchLostItemList(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher { return service.fetchLostItemList(requestModel: requestModel) } - func searchNoticeArticle(requestModel: SearchNoticeArticleRequest) -> AnyPublisher { + func searchNoticeArticle(requestModel: SearchNoticeArticleRequest) -> AnyPublisher { return service.searchNoticeArticle(requestModel: requestModel) } - func fetchNoticeData(requestModel: FetchNoticeDataRequest) -> AnyPublisher { + func fetchNoticeData(requestModel: FetchNoticeDataRequest) -> AnyPublisher { return service.fetchNoticeData(requestModel: requestModel) } - func fetchHotNoticeArticle() -> AnyPublisher<[NoticeArticleDto], Error> { + func fetchHotNoticeArticle() -> AnyPublisher<[NoticeArticleDto], ErrorResponse> { return service.fetchHotNoticeArticles() } @@ -69,7 +69,7 @@ final class DefaultNoticeListRepository: NoticeListRepository { return service.fetchMyNotificationKeyword() } - func fetchRecommendedKeyword(count: Int?) -> AnyPublisher { + func fetchRecommendedKeyword(count: Int?) -> AnyPublisher { return service.fetchRecommendedKeyword(count: count) } diff --git a/Koin/Data/Repository/DefaultShopRepository.swift b/Koin/Data/Repository/DefaultShopRepository.swift index 135c35799..09869289f 100644 --- a/Koin/Data/Repository/DefaultShopRepository.swift +++ b/Koin/Data/Repository/DefaultShopRepository.swift @@ -16,19 +16,19 @@ final class DefaultShopRepository: ShopRepository { self.service = service } - func fetchShopMenusCategoryList(shopId: Int) -> AnyPublisher { + func fetchShopMenusCategoryList(shopId: Int) -> AnyPublisher { service.fetchShopMenusCategory(shopId: shopId) } - func fetchShopSummary(id: Int) -> AnyPublisher { + func fetchShopSummary(id: Int) -> AnyPublisher { service.fetchShopSummary(id: id) } - func fetchBeneficialShops(id: Int) -> AnyPublisher { + func fetchBeneficialShops(id: Int) -> AnyPublisher { service.fetchBeneficialShops(id: id) } - func fetchShopBenefits() -> AnyPublisher { + func fetchShopBenefits() -> AnyPublisher { service.fetchShopBenefits() } @@ -36,32 +36,32 @@ final class DefaultShopRepository: ShopRepository { service.uploadFiles(files: files) } - func fetchShopList(requestModel: FetchShopListRequest) -> AnyPublisher { + func fetchShopList(requestModel: FetchShopListRequest) -> AnyPublisher { return service.fetchShopList(requestModel: requestModel) } - func fetchEventList() -> AnyPublisher { + func fetchEventList() -> AnyPublisher { return service.fetchEventList() } - func fetchShopCategoryList() -> AnyPublisher { + func fetchShopCategoryList() -> AnyPublisher { return service.fetchShopCategoryList() } - func fetchShopData(requestModel: FetchShopDataRequest) -> AnyPublisher { + func fetchShopData(requestModel: FetchShopDataRequest) -> AnyPublisher { return service.fetchShopData(requestModel: requestModel) } - func fetchShopMenuList(requestModel: FetchShopDataRequest) -> AnyPublisher { + func fetchShopMenuList(requestModel: FetchShopDataRequest) -> AnyPublisher { return service.fetchShopMenuList(requestModel: requestModel) } - func fetchShopEventList(requestModel: FetchShopDataRequest) -> AnyPublisher { + func fetchShopEventList(requestModel: FetchShopDataRequest) -> AnyPublisher { return service.fetchShopEventList(requestModel: requestModel) } func fetchReviewList(requestModel: FetchShopReviewRequest) -> AnyPublisher { - return service.fetchReviewList(requestModel: requestModel, retry: false) + return service.fetchReviewList(requestModel: requestModel) } func fetchReview(reviewId: Int, shopId: Int) -> AnyPublisher { @@ -92,11 +92,11 @@ final class DefaultShopRepository: ShopRepository { return service.postCallNotification(shopId: shopId) } - func searchRelatedQuery(text: String) -> AnyPublisher { // TODO: 삭제 예정 + func searchRelatedQuery(text: String) -> AnyPublisher { // TODO: 삭제 예정 return service.searchRelatedShops(text: text) } - func fetchSearchShop(requestModel: FetchShopSearchRequest) -> AnyPublisher { + func fetchSearchShop(requestModel: FetchShopSearchRequest) -> AnyPublisher { return service.fetchSearchShop(requestModel: requestModel) .map { dto in ShopSearch(from: dto) diff --git a/Koin/Data/Repository/DefaultTimetableRepository.swift b/Koin/Data/Repository/DefaultTimetableRepository.swift index 6e90da752..8e6744dd8 100644 --- a/Koin/Data/Repository/DefaultTimetableRepository.swift +++ b/Koin/Data/Repository/DefaultTimetableRepository.swift @@ -35,15 +35,15 @@ final class DefaultTimetableRepository: TimetableRepository { service.fetchMySemester() } - func fetchLectureList(semester: String) -> AnyPublisher<[SemesterLecture], Error> { + func fetchLectureList(semester: String) -> AnyPublisher<[SemesterLecture], ErrorResponse> { service.fetchLectureList(semester: semester) } - func fetchSemester() -> AnyPublisher<[SemesterDto], Error> { + func fetchSemester() -> AnyPublisher<[SemesterDto], ErrorResponse> { service.fetchSemester() } - func fetchDeptList() -> AnyPublisher<[DeptDto], Error> { + func fetchDeptList() -> AnyPublisher<[DeptDto], ErrorResponse> { return service.fetchDeptList() } diff --git a/Koin/Data/Repository/DefaultUserRepository.swift b/Koin/Data/Repository/DefaultUserRepository.swift index 078b26208..1fc09b8ea 100644 --- a/Koin/Data/Repository/DefaultUserRepository.swift +++ b/Koin/Data/Repository/DefaultUserRepository.swift @@ -63,11 +63,6 @@ final class DefaultUserRepository: UserRepository { service.login(requestModel: requestModel) } - func logout() -> AnyPublisher { - let fakeResponse = "" - return Just(fakeResponse).setFailureType(to: Error.self).eraseToAnyPublisher() - } - func findPassword(requestModel: FindPasswordRequest) -> AnyPublisher { service.findPassword(requestModel: requestModel) } @@ -80,11 +75,6 @@ final class DefaultUserRepository: UserRepository { service.checkDuplicatedNickname(requestModel: requestModel) } - func checkDuplicatedEmail(email: String) -> AnyPublisher { - let fakeResponse = "" - return Just(fakeResponse).setFailureType(to: Error.self).eraseToAnyPublisher() - } - func revoke() -> AnyPublisher { service.revoke() } diff --git a/Koin/Data/Service/AbTestService.swift b/Koin/Data/Service/AbTestService.swift index e7e10c063..9b07726d4 100644 --- a/Koin/Data/Service/AbTestService.swift +++ b/Koin/Data/Service/AbTestService.swift @@ -9,59 +9,38 @@ import Alamofire import Combine protocol AbTestService { - func assignAbTest(requestModel: AssignAbTestRequest, retry: Bool) -> AnyPublisher + func assignAbTest(requestModel: AssignAbTestRequest) -> AnyPublisher } final class DefaultAbTestService: AbTestService { private let networkService = NetworkService() - func assignAbTest(requestModel: AssignAbTestRequest, retry: Bool = false) -> AnyPublisher { - if KeychainWorker.shared.read(key: .accessHistoryId) == nil { - return self.assignAbTestToken() - .flatMap { _ in - self.assignAbTest(requestModel: requestModel) - } - .eraseToAnyPublisher() - } - - // 키체인이 있는 경우 API 요청 - return networkService.requestWithResponse(api: AbTestAPI.assignAbTest(requestModel)) - .handleEvents(receiveOutput: { response in - // KeyChain에 값 저장 - KeychainWorker.shared.create(key: .accessHistoryId, token: String(response.accessHistoryId)) - KeychainWorker.shared.create(key: .variableName, token: response.variableName.rawValue) - }) - .catch { error -> AnyPublisher in - - if error.code == "401" && !retry { - return self.networkService.refreshToken() - .flatMap { _ in self.assignAbTest(requestModel: requestModel) } // 재시도 - .catch { refreshError -> AnyPublisher in - return self.assignAbTest(requestModel: requestModel, retry: true) - } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() - } - - private func assignAbTestToken() -> AnyPublisher { - return networkService.requestWithResponse(api: AbTestAPI.assignAbTestToken) - .handleEvents(receiveOutput: { (response: AssignAbTestTokenResponse) in - KeychainWorker.shared.create(key: .accessHistoryId, token: String(response.accessHistoryId)) - }) - .map { _ in () } - .eraseToAnyPublisher() - } + func assignAbTest(requestModel: AssignAbTestRequest) -> AnyPublisher { + if KeychainWorker.shared.read(key: .accessHistoryId) == nil { + return self.assignAbTestToken() + .flatMap { _ in + self.assignAbTest(requestModel: requestModel) + } + .eraseToAnyPublisher() + } + + // 키체인이 있는 경우 API 요청 + return networkService.requestWithResponse(api: AbTestAPI.assignAbTest(requestModel)) + .handleEvents(receiveOutput: { response in + // KeyChain에 값 저장 + KeychainWorker.shared.create(key: .accessHistoryId, token: String(response.accessHistoryId)) + KeychainWorker.shared.create(key: .variableName, token: response.variableName.rawValue) + }) + .eraseToAnyPublisher() + } - private func request(_ api: AbTestAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } + private func assignAbTestToken() -> AnyPublisher { + return networkService.requestWithResponse(api: AbTestAPI.assignAbTestToken) + .handleEvents(receiveOutput: { (response: AssignAbTestTokenResponse) in + KeychainWorker.shared.create(key: .accessHistoryId, token: String(response.accessHistoryId)) + }) + .map { _ in () } .eraseToAnyPublisher() } } diff --git a/Koin/Data/Service/BusService.swift b/Koin/Data/Service/BusService.swift index 89b54a93f..a0815c8b5 100644 --- a/Koin/Data/Service/BusService.swift +++ b/Koin/Data/Service/BusService.swift @@ -9,47 +9,39 @@ import Alamofire import Combine protocol BusService { - func searchBusInformation(requestModel: SearchBusInfoRequest) -> AnyPublisher - func fetchShuttleRouteList() -> AnyPublisher - func fetchExpressTimetableList(requestModel: FetchBusTimetableRequest) -> AnyPublisher - func fetchCityTimetableList(requestModel: FetchCityBusTimetableRequest) -> AnyPublisher - func fetchEmergencyNotice() -> AnyPublisher - func fetchShuttleBusTimetable(id: String) -> AnyPublisher + func searchBusInformation(requestModel: SearchBusInfoRequest) -> AnyPublisher + func fetchShuttleRouteList() -> AnyPublisher + func fetchExpressTimetableList(requestModel: FetchBusTimetableRequest) -> AnyPublisher + func fetchCityTimetableList(requestModel: FetchCityBusTimetableRequest) -> AnyPublisher + func fetchEmergencyNotice() -> AnyPublisher + func fetchShuttleBusTimetable(id: String) -> AnyPublisher } final class DefaultBusService: BusService { - let mockNetworkService = MockNetworkService() + private let networkService = NetworkService() - func fetchExpressTimetableList(requestModel: FetchBusTimetableRequest) -> AnyPublisher { - return request(.fetchBusTimetableList(requestModel)) + func fetchExpressTimetableList(requestModel: FetchBusTimetableRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: BusAPI.fetchBusTimetableList(requestModel)) } - func searchBusInformation(requestModel: SearchBusInfoRequest) -> AnyPublisher { - return request(.searchBusInformation(requestModel)) + func searchBusInformation(requestModel: SearchBusInfoRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: BusAPI.searchBusInformation(requestModel)) } - func fetchShuttleRouteList() -> AnyPublisher { - return request(.fetchShuttleBusTimetableRoute) + func fetchShuttleRouteList() -> AnyPublisher { + return networkService.requestWithResponse(api: BusAPI.fetchShuttleBusTimetableRoute) } - func fetchCityTimetableList(requestModel: FetchCityBusTimetableRequest) -> AnyPublisher { - return request(.fetchCityBusTimetableList(requestModel)) + func fetchCityTimetableList(requestModel: FetchCityBusTimetableRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: BusAPI.fetchCityBusTimetableList(requestModel)) } - func fetchEmergencyNotice() -> AnyPublisher { - return request(.fetchEmergencyNotice) + func fetchEmergencyNotice() -> AnyPublisher { + return networkService.requestWithResponse(api: BusAPI.fetchEmergencyNotice) } - func fetchShuttleBusTimetable(id: String) -> AnyPublisher { - return request(.fetchShuttleBusTimetableList(id)) - } - - private func request(_ api: BusAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() + func fetchShuttleBusTimetable(id: String) -> AnyPublisher { + return networkService.requestWithResponse(api: BusAPI.fetchShuttleBusTimetableList(id)) } } diff --git a/Koin/Data/Service/ChatService.swift b/Koin/Data/Service/ChatService.swift index aa05c18da..578268a68 100644 --- a/Koin/Data/Service/ChatService.swift +++ b/Koin/Data/Service/ChatService.swift @@ -17,89 +17,26 @@ protocol ChatService { } final class DefaultChatService: ChatService { + + private let networkService = NetworkService() + func createChatRoom(articleId: Int) -> AnyPublisher { return networkService.requestWithResponse(api: ChatAPI.createChatRoom(articleId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: ChatAPI.createChatRoom(articleId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - private let networkService = NetworkService() - func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher { return networkService.request(api: ChatAPI.blockUser(articleId, chatRoomId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: ChatAPI.blockUser(articleId, chatRoomId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[ChatDetailDto], ErrorResponse> { return networkService.requestWithResponse(api: ChatAPI.fetchChatDetail(articleId, chatRoomId)) - .catch { [weak self] error -> AnyPublisher<[ChatDetailDto], ErrorResponse> in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: ChatAPI.fetchChatDetail(articleId, chatRoomId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func fetchChatRoom() -> AnyPublisher<[ChatRoomDto], ErrorResponse> { return networkService.requestWithResponse(api: ChatAPI.fetchChatRoom) - .catch { [weak self] error -> AnyPublisher<[ChatRoomDto], ErrorResponse> in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: ChatAPI.fetchChatRoom) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func postChatDetail(articleId: Int, chatRoomId: Int, request: PostChatDetailRequest) -> AnyPublisher { return networkService.requestWithResponse(api: ChatAPI.postChatDetail(articleId, chatRoomId, request)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: ChatAPI.postChatDetail(articleId, chatRoomId, request)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() - } - - - private func request(_ api: LandAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() } } diff --git a/Koin/Data/Service/CoreDataService.swift b/Koin/Data/Service/CoreData/CoreDataService.swift similarity index 100% rename from Koin/Data/Service/CoreDataService.swift rename to Koin/Data/Service/CoreData/CoreDataService.swift diff --git a/Koin/Data/Service/CoreService.swift b/Koin/Data/Service/CoreService.swift index 8c6483787..b5735f9e1 100644 --- a/Koin/Data/Service/CoreService.swift +++ b/Koin/Data/Service/CoreService.swift @@ -9,32 +9,29 @@ import Alamofire import Combine protocol CoreService { - func fetchVersion() -> AnyPublisher - func fetchBanner() -> AnyPublisher - func fetchClubCategories() -> AnyPublisher - func fetchHotClubs() -> AnyPublisher + func fetchVersion() -> AnyPublisher + func fetchBanner() -> AnyPublisher + func fetchClubCategories() -> AnyPublisher + func fetchHotClubs() -> AnyPublisher } final class DefaultCoreService: CoreService { - func fetchVersion() -> AnyPublisher { - return request(.checkVersion) - } - func fetchBanner() -> AnyPublisher { - return request(.fetchBanner) + + private let networkService = NetworkService() + + func fetchVersion() -> AnyPublisher { + return networkService.requestWithResponse(api: CoreAPI.checkVersion) } - func fetchClubCategories() -> AnyPublisher { - return request(.fetchClubCategories) + + func fetchBanner() -> AnyPublisher { + return networkService.requestWithResponse(api: CoreAPI.fetchBanner) } - func fetchHotClubs() -> AnyPublisher { - return request(.fetchHotClubs) + func fetchClubCategories() -> AnyPublisher { + return networkService.requestWithResponse(api: CoreAPI.fetchClubCategories) } - - private func request(_ api: CoreAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() + + func fetchHotClubs() -> AnyPublisher { + return networkService.requestWithResponse(api: CoreAPI.fetchHotClubs) } } diff --git a/Koin/Data/Service/DiningService.swift b/Koin/Data/Service/DiningService.swift index 777bcc1ab..9acdffcc7 100644 --- a/Koin/Data/Service/DiningService.swift +++ b/Koin/Data/Service/DiningService.swift @@ -9,8 +9,8 @@ import Alamofire import Combine protocol DiningService { - func fetchDiningList(requestModel: FetchDiningListRequest, retry: Bool) -> AnyPublisher<[DiningDto], ErrorResponse> - func fetchCoopShopList() -> AnyPublisher + func fetchDiningList(requestModel: FetchDiningListRequest) -> AnyPublisher<[DiningDto], ErrorResponse> + func fetchCoopShopList() -> AnyPublisher func diningLike(requestModel: DiningLikeRequest, isLiked: Bool) -> AnyPublisher } @@ -18,49 +18,16 @@ final class DefaultDiningService: DiningService { private let networkService = NetworkService() - func fetchDiningList(requestModel: FetchDiningListRequest, retry: Bool = false) -> AnyPublisher<[DiningDto], ErrorResponse> { + func fetchDiningList(requestModel: FetchDiningListRequest) -> AnyPublisher<[DiningDto], ErrorResponse> { return networkService.requestWithResponse(api: DiningAPI.fetchDiningList(requestModel)) - .catch { [weak self] error -> AnyPublisher<[DiningDto], ErrorResponse> in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" && !retry { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: DiningAPI.fetchDiningList(requestModel)) } - .catch { refreshError -> AnyPublisher<[DiningDto], ErrorResponse> in - return self.fetchDiningList(requestModel: requestModel, retry: true) - } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - func fetchCoopShopList() -> AnyPublisher { - return request(.fetchCoopShopList) + func fetchCoopShopList() -> AnyPublisher { + return networkService.requestWithResponse(api: DiningAPI.fetchCoopShopList) } func diningLike(requestModel: DiningLikeRequest, isLiked: Bool) -> AnyPublisher { return networkService.request(api: DiningAPI.diningLike(requestModel, isLiked)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: DiningAPI.diningLike(requestModel, isLiked)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() - } - - private func request(_ api: DiningAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() } } diff --git a/Koin/Data/Service/LandService.swift b/Koin/Data/Service/LandService.swift index cab4796b8..c11ad138e 100644 --- a/Koin/Data/Service/LandService.swift +++ b/Koin/Data/Service/LandService.swift @@ -9,25 +9,19 @@ import Alamofire import Combine protocol LandService { - func fetchLandList() -> AnyPublisher - func fetchLandDetail(requestModel: FetchLandDetailRequest) -> AnyPublisher + func fetchLandList() -> AnyPublisher + func fetchLandDetail(requestModel: FetchLandDetailRequest) -> AnyPublisher } final class DefaultLandService: LandService { - func fetchLandList() -> AnyPublisher { - return request(.fetchLandList) - } + private let networkService = NetworkService() - func fetchLandDetail(requestModel: FetchLandDetailRequest) -> AnyPublisher { - return request(.fetchLandDetail(requestModel)) + func fetchLandList() -> AnyPublisher { + return networkService.requestWithResponse(api: LandAPI.fetchLandList) } - - private func request(_ api: LandAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() + + func fetchLandDetail(requestModel: FetchLandDetailRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: LandAPI.fetchLandDetail(requestModel)) } } diff --git a/Koin/Data/Service/LogAnalyticsService.swift b/Koin/Data/Service/LogAnalyticsService.swift index d605b94b3..ffd182703 100644 --- a/Koin/Data/Service/LogAnalyticsService.swift +++ b/Koin/Data/Service/LogAnalyticsService.swift @@ -15,6 +15,7 @@ protocol LogAnalyticsService { } final class GA4AnalyticsService: LogAnalyticsService { + func logEvent(name: String, label: String, value: String, category: String) { let parameters = [ "event_label": label, diff --git a/Koin/Data/Service/LostItemService.swift b/Koin/Data/Service/LostItemService.swift index 8da04ae8e..1154e21d8 100644 --- a/Koin/Data/Service/LostItemService.swift +++ b/Koin/Data/Service/LostItemService.swift @@ -10,93 +10,39 @@ import Combine import Alamofire protocol LostItemService { - func fetchLostItemList(requestModel: FetchLostItemListRequest) -> AnyPublisher - func fetchLostItemData(id: Int) -> AnyPublisher + func fetchLostItemList(requestModel: FetchLostItemListRequest) -> AnyPublisher + func fetchLostItemData(id: Int) -> AnyPublisher func changeLostItemState(id: Int) -> AnyPublisher - func deleteLostItem(id: Int) -> AnyPublisher + func deleteLostItem(id: Int) -> AnyPublisher func updateLostItem(id: Int, requestModel: UpdateLostItemRequest) -> AnyPublisher - func fetchLostItemStats() -> AnyPublisher + func fetchLostItemStats() -> AnyPublisher } final class DefaultLostItemService: LostItemService { private let networkService = NetworkService() - func fetchLostItemList(requestModel: FetchLostItemListRequest) -> AnyPublisher { - return request(.fetchLostItemList(requestModel)) + func fetchLostItemList(requestModel: FetchLostItemListRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: LostItemAPI.fetchLostItemList(requestModel)) } - func fetchLostItemData(id: Int) -> AnyPublisher { - return request(.fetchLostItemData(id)) + func fetchLostItemData(id: Int) -> AnyPublisher { + return networkService.requestWithResponse(api: LostItemAPI.fetchLostItemData(id)) } func changeLostItemState(id: Int) -> AnyPublisher { return networkService.request(api: LostItemAPI.changeListItemState(id)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: LostItemAPI.changeListItemState(id)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - func deleteLostItem(id: Int) -> AnyPublisher { + func deleteLostItem(id: Int) -> AnyPublisher { return networkService.request(api: LostItemAPI.deleteLostItem(id)) - .catch { [weak self] error -> AnyPublisher in - guard let self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in - self.networkService.request(api: LostItemAPI.deleteLostItem(id)) - } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .mapError { $0 as Error } - .eraseToAnyPublisher() } func updateLostItem(id: Int, requestModel: UpdateLostItemRequest) -> AnyPublisher { return networkService.requestWithResponse(api: LostItemAPI.updateLostItem((id, requestModel))) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: LostItemAPI.updateLostItem((id, requestModel))) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - func fetchLostItemStats() -> AnyPublisher { - return request(LostItemAPI.fetchLostItemStats) - } - - private func request(_ api: LostItemAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() - } - - private func request(_ api: LostItemAPI) -> AnyPublisher { - return AF.request(api) - .validate() - .publishData() - .value() - .map { _ in } - .mapError { $0 as Error } - .eraseToAnyPublisher() + func fetchLostItemStats() -> AnyPublisher { + return networkService.requestWithResponse(api: LostItemAPI.fetchLostItemStats) } } diff --git a/Koin/Data/Service/MockNetworkService.swift b/Koin/Data/Service/MockNetworkService.swift deleted file mode 100644 index 9f8e5f2f3..000000000 --- a/Koin/Data/Service/MockNetworkService.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// MockNetworkService.swift -// koin -// -// Created by JOOMINKYUNG on 12/8/24. -// - -import Foundation -import Alamofire -import Combine - -class MockNetworkService { - func request(api: URLRequest) -> AnyPublisher { - - return AF.request(api) - .validate() - .publishData() - .tryMap { response in - guard let httpResponse = response.response else { - throw URLError(.badServerResponse) - } - - guard (200..<300).contains(httpResponse.statusCode) else { - if let data = response.data { - let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: data) - throw errorResponse - } else { - throw ErrorResponse(code: "", message: "알 수 없는 에러") - } - } - - guard let data = response.data else { - throw ErrorResponse(code: "", message: "응답이 없음") - } - - return try JSONDecoder().decode(T.self, from: data) - } - .mapError { error in - - return error - } - .eraseToAnyPublisher() - } -} diff --git a/Koin/Data/Service/Network/API/AbTestAPI.swift b/Koin/Data/Service/Network/API/AbTestAPI.swift index 556e19087..7b5866c7f 100644 --- a/Koin/Data/Service/Network/API/AbTestAPI.swift +++ b/Koin/Data/Service/Network/API/AbTestAPI.swift @@ -39,9 +39,6 @@ extension AbTestAPI: Router, URLRequestConvertible { if let accessHistoryId = KeychainWorker.shared.read(key: .accessHistoryId) { defaultHeaders["access_history_id"] = accessHistoryId } - if let token = KeychainWorker.shared.read(key: .access) { - defaultHeaders["Authorization"] = "Bearer \(token)" - } return defaultHeaders case .assignAbTestToken: return [:] diff --git a/Koin/Data/Service/Network/API/ChatAPI.swift b/Koin/Data/Service/Network/API/ChatAPI.swift index fb6c2eda0..9f651217f 100644 --- a/Koin/Data/Service/Network/API/ChatAPI.swift +++ b/Koin/Data/Service/Network/API/ChatAPI.swift @@ -42,12 +42,6 @@ extension ChatAPI: Router, URLRequestConvertible { public var headers: [String: String] { var baseHeaders: [String: String] = [:] switch self { - case .fetchChatRoom, .fetchChatDetail, .blockUser, .createChatRoom, .postChatDetail: - if let token = KeychainWorker.shared.read(key: .access) { - baseHeaders["Authorization"] = "Bearer \(token)" - } - } - switch self { case .createChatRoom, .postChatDetail: baseHeaders["Content-Type"] = "application/json" default: break diff --git a/Koin/Data/Service/Network/API/DiningAPI.swift b/Koin/Data/Service/Network/API/DiningAPI.swift index 294faae0f..0d8809a9f 100644 --- a/Koin/Data/Service/Network/API/DiningAPI.swift +++ b/Koin/Data/Service/Network/API/DiningAPI.swift @@ -38,16 +38,7 @@ extension DiningAPI: Router, URLRequestConvertible { } public var headers: [String: String] { - switch self { - case .fetchCoopShopList: return [:] - case .diningLike, .fetchDiningList: - if let token = KeychainWorker.shared.read(key: .access) { - let headers = ["Authorization": "Bearer \(token)"] - return headers - } else { - return [:] - } - } + return [:] } public var parameters: Any? { diff --git a/Koin/Data/Service/Network/API/LostItemAPI.swift b/Koin/Data/Service/Network/API/LostItemAPI.swift index 447fc93f4..12ef20c73 100644 --- a/Koin/Data/Service/Network/API/LostItemAPI.swift +++ b/Koin/Data/Service/Network/API/LostItemAPI.swift @@ -45,17 +45,7 @@ extension LostItemAPI: Router, URLRequestConvertible { } public var headers: [String: String] { - switch self { - case .fetchLostItemList, .fetchLostItemData, .changeListItemState, .deleteLostItem, .updateLostItem: - if let token = KeychainWorker.shared.read(key: .access) { - let headers = ["Authorization": "Bearer \(token)"] - return headers - } else { - return [:] - } - case .fetchLostItemStats: - return [:] - } + return [:] } public var parameters: Any? { diff --git a/Koin/Data/Service/Network/API/NotiAPI.swift b/Koin/Data/Service/Network/API/NotiAPI.swift index 30419e799..fbcf70597 100644 --- a/Koin/Data/Service/Network/API/NotiAPI.swift +++ b/Koin/Data/Service/Network/API/NotiAPI.swift @@ -44,13 +44,7 @@ extension NotiAPI: Router, URLRequestConvertible { } public var headers: [String: String] { - if let token = KeychainWorker.shared.read(key: .access) { - let headers = ["Authorization": "Bearer \(token)", - "Content-Type": "application/json" ] - return headers - } else { - return [:] - } + return ["Content-Type": "application/json"] } public var parameters: Any? { diff --git a/Koin/Data/Service/Network/API/NoticeListAPI.swift b/Koin/Data/Service/Network/API/NoticeListAPI.swift index 0660df67a..537b169e4 100644 --- a/Koin/Data/Service/Network/API/NoticeListAPI.swift +++ b/Koin/Data/Service/Network/API/NoticeListAPI.swift @@ -64,17 +64,7 @@ extension NoticeListAPI: Router, URLRequestConvertible { } public var headers: [String: String] { - switch self { - case .fetchNoticeArticles, .searchNoticeArticle, .fetchNoticeData, .fetchHotNoticeArticles, .fetchRecommendedKeyword, .fetchRecommendedSearchWord, .fetchLostItemList: - return [:] - case .createNotificationKeyword, .deleteNotificationKeyword, .fetchNotificationKeyword, .postLostItem, .deleteLostItem, .fetchLostItem, .reportLostItem, .fetchLostItemArticles: - if let token = KeychainWorker.shared.read(key: .access) { - let headers = ["Authorization": "Bearer \(token)"] - return headers - } else { - return [:] - } - } + return [:] } diff --git a/Koin/Data/Service/Network/API/ShopAPI.swift b/Koin/Data/Service/Network/API/ShopAPI.swift index 014f907cb..f1e279487 100644 --- a/Koin/Data/Service/Network/API/ShopAPI.swift +++ b/Koin/Data/Service/Network/API/ShopAPI.swift @@ -80,13 +80,6 @@ extension ShopAPI: Router, URLRequestConvertible { public var headers: [String: String] { var baseHeaders: [String: String] = [:] switch self { - case .fetchShopList, .fetchEventList, .fetchShopCategoryList, .fetchShopData, .fetchShopMenuList, .fetchShopEventList, .fetchShopBenefits, .searchShop: break - default: - if let token = KeychainWorker.shared.read(key: .access) { - baseHeaders["Authorization"] = "Bearer \(token)" - } - } - switch self { case .postReview, .reportReview, .modifyReview, .deleteReview, .postCallNotification: baseHeaders["Content-Type"] = "application/json" case .uploadFiles: diff --git a/Koin/Data/Service/Network/API/TimetableAPI.swift b/Koin/Data/Service/Network/API/TimetableAPI.swift index 854ac7b3d..cfd444528 100644 --- a/Koin/Data/Service/Network/API/TimetableAPI.swift +++ b/Koin/Data/Service/Network/API/TimetableAPI.swift @@ -64,13 +64,6 @@ extension TimetableAPI: Router, URLRequestConvertible { public var headers: [String: String] { var baseHeaders: [String: String] = [:] switch self { - case .fetchFrame, .deleteFrame, .createFrame, .modifyFrame, .postLecture, .modifyLecture, .fetchLecture, .fetchMySemester, .deleteLecture, .deleteSemester, ._deleteLecture, .rollbackFrame, .fetchAllFrames: - if let token = KeychainWorker.shared.read(key: .access) { - baseHeaders["Authorization"] = "Bearer \(token)" - } - default: break - } - switch self { case .createFrame, .modifyFrame, .postLecture, .modifyLecture, .rollbackFrame: baseHeaders["Content-Type"] = "application/json" default: diff --git a/Koin/Data/Service/Network/API/UserAPI.swift b/Koin/Data/Service/Network/API/UserAPI.swift index 8020bc7d8..892779f08 100644 --- a/Koin/Data/Service/Network/API/UserAPI.swift +++ b/Koin/Data/Service/Network/API/UserAPI.swift @@ -92,14 +92,7 @@ extension UserAPI: Router, URLRequestConvertible { case .fetchStudentUserData, .revoke, .checkAuth, .checkLogin, .fetchGeneralUserData: break } - switch self { - case .fetchStudentUserData, .revoke, .modifyStudentUserData, .checkPassword, .checkAuth , .fetchGeneralUserData, .modifyGeneralUserData, .changePassword: - if let token = KeychainWorker.shared.read(key: .access) { - baseHeaders["Authorization"] = "Bearer \(token)" - } - default: break - } - return baseHeaders + return baseHeaders } public var parameters: Any? { diff --git a/Koin/Data/Service/Network/Interceptor.swift b/Koin/Data/Service/Network/Interceptor.swift new file mode 100644 index 000000000..4863e4847 --- /dev/null +++ b/Koin/Data/Service/Network/Interceptor.swift @@ -0,0 +1,117 @@ +// +// Interceptor.swift +// koin +// +// Created by 홍기정 on 2/12/26. +// + +import Foundation +import Alamofire +import Combine + +final class Interceptor: RequestInterceptor { + + private let retryLimit = 1 + private var subscriptions: Set = [] + + private let lock = NSLock() + @Published private var isRefreshing = false + + func adapt(_ urlRequest: URLRequest, + for session: Session, + completion: @escaping (Result) -> Void) { + + lock.lock() + defer { + lock.unlock() + } + + switch isRefreshing { + case true: + $isRefreshing + .filter { $0 == false } + .first() + .sink { _ in + var urlRequest = urlRequest + if let token = KeychainWorker.shared.read(key: .access) { + urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + } + completion(.success(urlRequest)) + } + .store(in: &subscriptions) + case false: + var urlRequest = urlRequest + if let token = KeychainWorker.shared.read(key: .access) { + urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + } + completion(.success(urlRequest)) + } + } + + func retry(_ request: Request, + for session: Session, + dueTo error: any Error, + completion: @escaping @Sendable (RetryResult) -> Void) { + + guard let responseCode = request.response?.statusCode, + responseCode == 401, + request.retryCount < retryLimit, + let _ = KeychainWorker.shared.read(key: .refresh) else { + completion(.doNotRetryWithError(error)) + return + } + + lock.lock() + defer { + lock.unlock() + } + + switch isRefreshing { + case true: + $isRefreshing + .filter { $0 == false } + .first() + .sink { _ in + completion(.retry) + } + .store(in: &subscriptions) + case false: + isRefreshing = true + refreshToken().sink( receiveValue: { [weak self] isRefreshed in + guard let self else { return } + + lock.lock() + isRefreshing = false + lock.unlock() + + completion(isRefreshed ? .retry : .doNotRetryWithError(error)) + }).store(in: &subscriptions) + } + } +} + +extension Interceptor { + + private func refreshToken() -> AnyPublisher { + return requestWithResponse(api: UserAPI.refreshToken(RefreshTokenRequest(refreshToken: KeychainWorker.shared.read(key: .refresh) ?? ""))) + .map { (tokenDto: TokenDto) in + KeychainWorker.shared.create(key: .access, token: tokenDto.token) + KeychainWorker.shared.create(key: .refresh, token: tokenDto.refreshToken) + return true + } + .catch { _ -> Just in + KeychainWorker.shared.delete(key: .access) + KeychainWorker.shared.delete(key: .refresh) + return Just(false) + } + .eraseToAnyPublisher() + } + + private func requestWithResponse(api: URLRequestConvertible) -> AnyPublisher { + return AF.request(api) + .publishDecodable(type: T.self) + .value() + .mapError { $0 as Error } + .eraseToAnyPublisher() + } +} diff --git a/Koin/Data/Service/Network/MockNetworkService.swift b/Koin/Data/Service/Network/MockNetworkService.swift new file mode 100644 index 000000000..99e2b2b13 --- /dev/null +++ b/Koin/Data/Service/Network/MockNetworkService.swift @@ -0,0 +1,52 @@ +// +// MockNetworkService.swift +// koin +// +// Created by JOOMINKYUNG on 12/8/24. +// + +import Foundation +import Alamofire +import Combine + +class MockNetworkService { + func request(api: URLRequest) -> AnyPublisher { + + return AF.request(api) + .validate() + .publishData() + .tryMap { response in + guard let httpResponse = response.response else { + throw ErrorResponse.networkError + } + + guard (200..<300).contains(httpResponse.statusCode) else { + if let data = response.data { + if let errorResponse = try? JSONDecoder().decode(ErrorResponseDto.self, from: data) { + throw errorResponse.toDomain(withStatusCode: httpResponse.statusCode) + } + throw ErrorResponse.decodingError(httpResponse.statusCode) + } else { + throw ErrorResponse.emptyDataError(httpResponse.statusCode) + } + } + + guard let data = response.data else { + throw ErrorResponse.emptyDataError(httpResponse.statusCode) + } + + if let response = try? JSONDecoder().decode(T.self, from: data) { + return response + } else { + throw ErrorResponse.decodingError(httpResponse.statusCode) + } + } + .mapError { error in + if let errorResponse = error as? ErrorResponse { + return errorResponse + } + return ErrorResponse.typeCastingError + } + .eraseToAnyPublisher() + } +} diff --git a/Koin/Data/Service/Network/NetworkService.swift b/Koin/Data/Service/Network/NetworkService.swift new file mode 100644 index 000000000..4b315df61 --- /dev/null +++ b/Koin/Data/Service/Network/NetworkService.swift @@ -0,0 +1,159 @@ +// +// NetworkService.swift +// koin +// +// Created by 김나훈 on 7/27/24. +// + +import Foundation +import Alamofire +import Combine + +final class NetworkService { + + private let interceptor = Interceptor() + + func request(api: URLRequestConvertible) -> AnyPublisher { + return AF.request(api, interceptor: interceptor) + .publishData() + .tryMap { response in + guard let httpResponse = response.response else { + throw ErrorResponse.networkError + } + if 200..<300 ~= httpResponse.statusCode { + return () + } + if let data = response.data { + if let errorResponse = try? JSONDecoder().decode(ErrorResponseDto.self, from: data) { + throw errorResponse.toDomain(withStatusCode: httpResponse.statusCode) + } + throw ErrorResponse.decodingError(httpResponse.statusCode) + } + throw ErrorResponse.emptyDataError(httpResponse.statusCode) + } + .mapError { error -> ErrorResponse in + self.handleError(error) + } + .eraseToAnyPublisher() + } + + func requestWithResponse(api: URLRequestConvertible) -> AnyPublisher { + return AF.request(api, interceptor: interceptor) + .publishData() + .tryMap { response in + guard let httpResponse = response.response else { + throw ErrorResponse.networkError + } + if 200..<300 ~= httpResponse.statusCode { + if let data = response.data { + if let decodedResponse = try? JSONDecoder().decode(T.self, from: data) { + return decodedResponse + } + throw ErrorResponse.decodingError(httpResponse.statusCode) + } + throw ErrorResponse.emptyDataError(httpResponse.statusCode) + } + if let data = response.data { + if let errorResponse = try? JSONDecoder().decode(ErrorResponseDto.self, from: data) { + throw errorResponse.toDomain(withStatusCode: httpResponse.statusCode) + } + throw ErrorResponse.decodingError(httpResponse.statusCode) + } + throw ErrorResponse.emptyDataError(httpResponse.statusCode) + } + .mapError { error -> ErrorResponse in + self.handleError(error) + } + .eraseToAnyPublisher() + } + + func uploadFiles(api: ShopAPI) -> AnyPublisher { + guard case ShopAPI.uploadFiles(let files) = api else { + return Fail(error: ErrorResponse.invalidApi) + .eraseToAnyPublisher() + } + + return AF.upload(multipartFormData: api.asMultipartFormData(data: files), + to: api.baseURL + api.path, + method: api.method, + headers: Alamofire.HTTPHeaders(api.headers), + interceptor: interceptor) + .publishData() + .tryMap { response in + guard let httpResponse = response.response else { + throw ErrorResponse.networkError + } + if 200..<300 ~= httpResponse.statusCode { + if let data = response.data { + if let decodedResponse = try? JSONDecoder().decode(FileUploadResponse.self, from: data) { + return decodedResponse + } + throw ErrorResponse.decodingError(httpResponse.statusCode) + } + throw ErrorResponse.emptyDataError(httpResponse.statusCode) + } + if let data = response.data { + if let errorResponse = try? JSONDecoder().decode(ErrorResponseDto.self, from: data) { + throw errorResponse.toDomain(withStatusCode: httpResponse.statusCode) + } + throw ErrorResponse.decodingError(httpResponse.statusCode) + } + throw ErrorResponse.emptyDataError(httpResponse.statusCode) + } + .mapError { error -> ErrorResponse in + self.handleError(error) + } + .eraseToAnyPublisher() + } + + func downloadFiles(api: URLRequest, fileName: String) -> AnyPublisher { + let fileManager = FileManager.default + guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return Fail(error: ErrorResponse.fileManagerFailedDirectory).eraseToAnyPublisher() } + let fileUrl = documentsDirectory.appendingPathComponent(fileName) + let destination: DownloadRequest.Destination = { _, _ in + return (fileUrl, [.removePreviousFile, .createIntermediateDirectories]) + } + + return AF.download(api, interceptor: interceptor, to: destination) + .publishData() + .tryMap { response in + guard let httpResponse = response.response else { + throw ErrorResponse.networkError + } + if 200..<300 ~= httpResponse.statusCode { + return documentsDirectory + } + if let data = response.value { + if let errorRespose = try? JSONDecoder().decode(ErrorResponseDto.self, from: data) { + throw errorRespose.toDomain(withStatusCode: httpResponse.statusCode) + } + throw ErrorResponse.decodingError(httpResponse.statusCode) + } + throw ErrorResponse.emptyDataError(httpResponse.statusCode) + } + .mapError { error -> ErrorResponse in + self.handleError(error) + } + .eraseToAnyPublisher() + + } +} + +extension NetworkService { + + private func handleError(_ error: Error) -> ErrorResponse { + if let errorResponse = error as? ErrorResponse { + + if let statusCode = errorResponse.statusCode, + 500..<600 ~= statusCode { + NotificationCenter.default.post(name: NSNotification.Name("ServerError"), object: nil) + } + + Log.make().error("\(errorResponse)") + return errorResponse + } + + Log.make().error("\(error)") + return ErrorResponse.typeCastingError + } +} diff --git a/Koin/Data/Service/Network/Router.swift b/Koin/Data/Service/Network/Router.swift index 5e94096d3..f63e87689 100644 --- a/Koin/Data/Service/Network/Router.swift +++ b/Koin/Data/Service/Network/Router.swift @@ -8,7 +8,7 @@ import Foundation import Alamofire -public protocol Router { +public protocol Router: URLRequestConvertible { var baseURL: String { get } var path: String { get } var method: Alamofire.HTTPMethod { get } @@ -37,14 +37,15 @@ extension Router { return request } - public func asMultipartRequest(data: [Data], withName: String, fileName: String, mimeType: String) -> DataRequest { - return AF.upload(multipartFormData: { multipartFormData in - for (index, fileData) in data.enumerated() { - let uniqueFileName = "\(fileName)_\(index + 1)" - multipartFormData.append(fileData, withName: withName, fileName: uniqueFileName, mimeType: mimeType) - } - }, to: baseURL + path, method: method, headers: Alamofire.HTTPHeaders(headers)) - } + + public func asMultipartFormData(data: [Data]) -> ((MultipartFormData)->Void) { + return { multipartFormData in + for (index, fileData) in data.enumerated() { + let uniqueFileName = "file_\(index + 1)" + multipartFormData.append(fileData, withName: "files", fileName: uniqueFileName, mimeType: "image/png") + } + } + } } extension Encodable { diff --git a/Koin/Data/Service/NetworkService.swift b/Koin/Data/Service/NetworkService.swift deleted file mode 100644 index 14cdedbd2..000000000 --- a/Koin/Data/Service/NetworkService.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// NetworkService.swift -// koin -// -// Created by 김나훈 on 7/27/24. -// - -import Foundation -import Alamofire -import Combine - -class NetworkService { - func request(api: URLRequestConvertible) -> AnyPublisher { - return AF.request(api) - .validate() - .publishData() - .tryMap { response in - guard let httpResponse = response.response else { - throw URLError(.badServerResponse) - } - if 200..<300 ~= httpResponse.statusCode { - return () - } else if httpResponse.statusCode == 401 { - throw ErrorResponse(code: "401", message: "") - } else { - if let data = response.data { - let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: data) - throw errorResponse - } else { - let afError = AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: httpResponse.statusCode)) - throw afError - } - } - } - .mapError { error -> ErrorResponse in - self.handleError(error) - } - .eraseToAnyPublisher() - } - - func requestWithResponse(api: URLRequestConvertible) -> AnyPublisher { - return AF.request(api) - .validate() - .publishData() - .tryMap { response in - guard let httpResponse = response.response else { - throw URLError(.badServerResponse) - } - if 200..<300 ~= httpResponse.statusCode { - guard let data = response.data else { - throw ErrorResponse(code: "", message: "알 수 없는 에러") - } - let decodedResponse = try JSONDecoder().decode(T.self, from: data) - return decodedResponse - } else if httpResponse.statusCode == 401 { - throw ErrorResponse(code: "401", message: "") - } else { - if let data = response.data { - let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: data) - throw errorResponse - } else { - let afError = AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: httpResponse.statusCode)) - throw afError - } - } - } - .mapError { error -> ErrorResponse in - self.handleError(error) - } - .eraseToAnyPublisher() - } - - func handleError(_ error: Error) -> ErrorResponse { - if let errorResponse = error as? ErrorResponse { - return errorResponse - } - return ErrorResponse(code: "", message: "알 수 없는 에러") - } - - func refreshToken() -> AnyPublisher { - return requestWithResponse(api: UserAPI.refreshToken(RefreshTokenRequest(refreshToken: KeychainWorker.shared.read(key: .refresh) ?? ""))) - .map { (tokenDto: TokenDto) -> Void in - KeychainWorker.shared.create(key: .access, token: tokenDto.token) - KeychainWorker.shared.create(key: .refresh, token: tokenDto.refreshToken) - return () - } - .catch { error -> AnyPublisher in - KeychainWorker.shared.delete(key: .access) - KeychainWorker.shared.delete(key: .refresh) - return Fail(error: ErrorResponse(code: "401", message: "로그인이 필요한 기능이에요.")).eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } - - func uploadFiles(api: ShopAPI) -> AnyPublisher { - guard case ShopAPI.uploadFiles(let files) = api else { - return Fail(error: ErrorResponse(code: "invalid_api", message: "Invalid API case for file upload")) - .eraseToAnyPublisher() - } - - return Future { promise in - api.asMultipartRequest(data: files, withName: "files", fileName: "file", mimeType: "image/png") - .responseDecodable(of: FileUploadResponse.self) { response in - switch response.result { - case .success(let fileUploadResponse): - promise(.success(fileUploadResponse)) - case .failure: - if let data = response.data { - let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) - promise(.failure(errorResponse ?? ErrorResponse(code: "unknown", message: "An unknown error occurred"))) - } else { - promise(.failure(ErrorResponse(code: "unknown", message: "An unknown error occurred"))) - } - } - } - } - .eraseToAnyPublisher() - } - - func downloadFiles(api: URLRequest, fileName: String) -> AnyPublisher { - let fileManager = FileManager.default - guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return Fail(error: ErrorResponse(code: "001", message: "파일 저장 위치 찾기 실패")).eraseToAnyPublisher() } - let fileUrl = documentsDirectory.appendingPathComponent(fileName) - let destination: DownloadRequest.Destination = { _, _ in - return (fileUrl, [.removePreviousFile, .createIntermediateDirectories]) - } - - return AF.download(api, to: destination) - .validate() - .publishData() - .tryMap { response in - guard let httpResponse = response.response else { - throw URLError(.badServerResponse) - } - - if 200..<300 ~= httpResponse.statusCode { - print("File is downloaded") - return documentsDirectory - } else { - if let error = response.error { - print("Download Failed - \(error)") - } else { - throw ErrorResponse(code: "\(httpResponse.statusCode)", message: "알 수 없는 에러") - } - } - return nil - } - .mapError { error -> ErrorResponse in - self.handleError(error) - } - .eraseToAnyPublisher() - - } -} diff --git a/Koin/Data/Service/NotiService.swift b/Koin/Data/Service/NotiService.swift index 485b642e1..980612dc0 100644 --- a/Koin/Data/Service/NotiService.swift +++ b/Koin/Data/Service/NotiService.swift @@ -20,67 +20,24 @@ final class DefaultNotiService: NotiService { private let networkService = NetworkService() private func sendDeviceToken() -> AnyPublisher { - return networkService.request(api: NotiAPI.sendDeviceToken) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: NotiAPI.sendDeviceToken) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() - } + return networkService.request(api: NotiAPI.sendDeviceToken) + } - func changeNoti(method: Alamofire.HTTPMethod, requestModel: NotiSubscribeRequest) -> AnyPublisher { - return sendDeviceToken() // 1. 먼저 sendDeviceToken()을 호출 - .flatMap { [weak self] _ -> AnyPublisher in - guard let self = self else { return Fail(error: ErrorResponse(code: "500", message: "Unexpected error")).eraseToAnyPublisher() } - return self.networkService.request(api: NotiAPI.changeNoti(method, requestModel)) // 2. 성공하면 changeNoti 호출 - } - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: NotiAPI.changeNoti(method, requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() - } + func changeNoti(method: Alamofire.HTTPMethod, requestModel: NotiSubscribeRequest) -> AnyPublisher { + return sendDeviceToken() // 1. 먼저 sendDeviceToken()을 호출 + .flatMap { [weak self] _ -> AnyPublisher in + guard let self = self else { return Fail(error: ErrorResponse.unexpectedInternalError).eraseToAnyPublisher() } + return self.networkService.request(api: NotiAPI.changeNoti(method, requestModel)) // 2. 성공하면 changeNoti 호출 + } + .eraseToAnyPublisher() + } func changeNotiDetail(method: Alamofire.HTTPMethod, requestModel: NotiSubscribeDetailRequest) -> AnyPublisher { return networkService.request(api: NotiAPI.changeNotiDetail(method, requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: NotiAPI.changeNotiDetail(method, requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func fetchNotiList() -> AnyPublisher { return networkService.requestWithResponse(api: NotiAPI.fetchNotiList) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: NotiAPI.fetchNotiList) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } } diff --git a/Koin/Data/Service/NoticeListService.swift b/Koin/Data/Service/NoticeListService.swift index 1b4319602..cc81135e5 100644 --- a/Koin/Data/Service/NoticeListService.swift +++ b/Koin/Data/Service/NoticeListService.swift @@ -10,147 +10,75 @@ import Alamofire import Combine protocol NoticeListService { - func fetchNoticeArticles(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher - func searchNoticeArticle(requestModel: SearchNoticeArticleRequest) -> AnyPublisher - func fetchLostItemArticles(requestModel: FetchLostItemsRequest, retry: Bool) -> AnyPublisher - func fetchNoticeData(requestModel: FetchNoticeDataRequest) -> AnyPublisher - func fetchHotNoticeArticles() -> AnyPublisher<[NoticeArticleDto], Error> + func fetchNoticeArticles(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher + func searchNoticeArticle(requestModel: SearchNoticeArticleRequest) -> AnyPublisher + func fetchLostItemArticles(requestModel: FetchLostItemsRequest) -> AnyPublisher + func fetchNoticeData(requestModel: FetchNoticeDataRequest) -> AnyPublisher + func fetchHotNoticeArticles() -> AnyPublisher<[NoticeArticleDto], ErrorResponse> func createNotificationKeyword(requestModel: NoticeKeywordDto) -> AnyPublisher func deleteNotificationKeyword(requestModel: NoticeKeywordDto) -> AnyPublisher func fetchMyNotificationKeyword() -> AnyPublisher - func fetchRecommendedKeyword(count: Int?) -> AnyPublisher + func fetchRecommendedKeyword(count: Int?) -> AnyPublisher func downloadNoticeAttachment(downloadUrl: String, fileName: String) -> AnyPublisher func manageRecentSearchedWord(name: String, date: Date, actionType: Int) func fetchRecentSearchedWord() -> [RecentSearchedWordInfo] func postLostItem(request: [PostLostItemRequest]) -> AnyPublisher - func fetchLostItemList(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher - func fetchLostItem(id: Int, retry: Bool) -> AnyPublisher + func fetchLostItemList(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher + func fetchLostItem(id: Int) -> AnyPublisher func deleteLostItem(id: Int) -> AnyPublisher func reportLostItemArticle(id: Int, request: ReportLostItemRequest) -> AnyPublisher } final class DefaultNoticeService: NoticeListService { - let networkService = NetworkService() - let coreDataService = CoreDataService.shared + private let networkService = NetworkService() + private let coreDataService = CoreDataService.shared - func fetchLostItemArticles(requestModel: FetchLostItemsRequest, retry: Bool) -> AnyPublisher { + func fetchLostItemArticles(requestModel: FetchLostItemsRequest) -> AnyPublisher { return networkService.requestWithResponse(api: NoticeListAPI.fetchLostItemArticles(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" && !retry { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: NoticeListAPI.fetchLostItemArticles(requestModel)) } - .catch { refreshError -> AnyPublisher in - return self.fetchLostItemArticles(requestModel: requestModel, retry: true) - } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - func reportLostItemArticle(id: Int, request: ReportLostItemRequest) -> AnyPublisher { return networkService.request(api: NoticeListAPI.reportLostItem(id, request)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: NoticeListAPI.reportLostItem(id, request)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func postLostItem(request: [PostLostItemRequest]) -> AnyPublisher { return networkService.requestWithResponse(api: NoticeListAPI.postLostItem(request)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: NoticeListAPI.postLostItem(request)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - func fetchLostItemList(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher { - return request(.fetchLostItemList(requestModel)) + func fetchLostItemList(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: NoticeListAPI.fetchLostItemList(requestModel)) } - func fetchLostItem(id: Int, retry: Bool = false) -> AnyPublisher { + + func fetchLostItem(id: Int) -> AnyPublisher { return networkService.requestWithResponse(api: NoticeListAPI.fetchLostItem(id)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" && !retry { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: NoticeListAPI.fetchLostItem(id)) } - .catch { refreshError -> AnyPublisher in - return self.fetchLostItem(id: id, retry: true) - } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func deleteLostItem(id: Int) -> AnyPublisher { return networkService.request(api: NoticeListAPI.deleteLostItem(id)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: NoticeListAPI.deleteLostItem(id)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - func fetchNoticeArticles(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher { - return request(.fetchNoticeArticles(requestModel)) + func fetchNoticeArticles(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: NoticeListAPI.fetchNoticeArticles(requestModel)) } - func searchNoticeArticle(requestModel: SearchNoticeArticleRequest) -> AnyPublisher { - return request(.searchNoticeArticle(requestModel)) + func searchNoticeArticle(requestModel: SearchNoticeArticleRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: NoticeListAPI.searchNoticeArticle(requestModel)) } - func fetchNoticeData(requestModel: FetchNoticeDataRequest) -> AnyPublisher { - return request(.fetchNoticeData(requestModel)) + func fetchNoticeData(requestModel: FetchNoticeDataRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: NoticeListAPI.fetchNoticeData(requestModel)) } - func fetchHotNoticeArticles() -> AnyPublisher<[NoticeArticleDto], Error> { - return request(.fetchHotNoticeArticles) + func fetchHotNoticeArticles() -> AnyPublisher<[NoticeArticleDto], ErrorResponse> { + return networkService.requestWithResponse(api: NoticeListAPI.fetchHotNoticeArticles) } func createNotificationKeyword(requestModel: NoticeKeywordDto) -> AnyPublisher { return networkService.requestWithResponse(api: NoticeListAPI.createNotificationKeyword(requestModel)) .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: NoticeListAPI.createNotificationKeyword(requestModel)) - } - .catch { [weak self] _ -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - return self.createCoreDataKeyword(requestModel: requestModel) - } - .eraseToAnyPublisher() - } else { - return self.createCoreDataKeyword(requestModel: requestModel) - } + guard let self else { return Fail(error: error).eraseToAnyPublisher() } + return self.createCoreDataKeyword(requestModel: requestModel) } .eraseToAnyPublisher() } @@ -158,18 +86,6 @@ final class DefaultNoticeService: NoticeListService { func deleteNotificationKeyword(requestModel: NoticeKeywordDto) -> AnyPublisher { if let id = requestModel.id { return networkService.request(api: NoticeListAPI.deleteNotificationKeyword(id)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: NoticeListAPI.deleteNotificationKeyword(id)) - } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } else { if let existingKeywords = coreDataService.fetchEntities(objectType: NoticeKeywordInformation.self, predicate: NSPredicate(format: "name == %@", requestModel.keyword)) { @@ -177,7 +93,7 @@ final class DefaultNoticeService: NoticeListService { coreDataService.delete(deletedObject: deletedKeyword) } } - return Fail(error: ErrorResponse(code: "", message: "로그인에 실패하여 코어데이터에서 키워드 삭제")).eraseToAnyPublisher() + return Fail(error: ErrorResponse.deleteKeywordError).eraseToAnyPublisher() } } @@ -186,34 +102,24 @@ final class DefaultNoticeService: NoticeListService { .map { NoticeKeywordsFetchResult.success($0) } .catch { [weak self] error -> AnyPublisher in guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: NoticeListAPI.fetchNotificationKeyword).map { NoticeKeywordsFetchResult.success($0) } - } - .catch { [weak self] _ -> AnyPublisher in - guard let self = self else { return Fail(error: error.self).eraseToAnyPublisher() } - return self.fetchCoreDataKeyword() - }.eraseToAnyPublisher() - } else { - return fetchCoreDataKeyword() - } + return self.fetchCoreDataKeyword() } .eraseToAnyPublisher() } - func fetchRecommendedKeyword(count: Int?) -> AnyPublisher { + func fetchRecommendedKeyword(count: Int?) -> AnyPublisher { if let count = count { let requestModel = FetchRecommendedSearchWordRequest(count: count) - return request(.fetchRecommendedSearchWord(requestModel)).eraseToAnyPublisher() + return networkService.requestWithResponse(api: NoticeListAPI.fetchRecommendedSearchWord(requestModel)).eraseToAnyPublisher() } else { - return request(.fetchRecommendedKeyword).eraseToAnyPublisher() + return networkService.requestWithResponse(api: NoticeListAPI.fetchRecommendedKeyword).eraseToAnyPublisher() } } func downloadNoticeAttachment(downloadUrl: String, fileName: String) -> AnyPublisher { guard let url = URL(string: downloadUrl) else { - return Fail(error: ErrorResponse(code: "", message: "URL이 유효하지 않습니다.")) + return Fail(error: ErrorResponse.invalidUrl) .eraseToAnyPublisher() } var api = URLRequest(url: url) @@ -270,14 +176,6 @@ final class DefaultNoticeService: NoticeListService { let keyword = NoticeKeywordInformation(context: self.coreDataService.context) keyword.name = requestModel.keyword self.coreDataService.insert(insertedObject: keyword) - return Fail(error: ErrorResponse(code: "", message: "로그인에 실패하여 코어데이터에 키워드 저장")).eraseToAnyPublisher() - } - - private func request(_ api: NoticeListAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() + return Fail(error: ErrorResponse.createKeywordError).eraseToAnyPublisher() } } diff --git a/Koin/Data/Service/ShopService.swift b/Koin/Data/Service/ShopService.swift index c17c06d53..36212dcb1 100644 --- a/Koin/Data/Service/ShopService.swift +++ b/Koin/Data/Service/ShopService.swift @@ -10,30 +10,30 @@ import Alamofire import Combine protocol ShopService { - func fetchShopMenusCategory(shopId: Int) -> AnyPublisher - func fetchShopSummary(id: Int) -> AnyPublisher - func fetchShopList(requestModel: FetchShopListRequest) -> AnyPublisher - func fetchEventList() -> AnyPublisher - func fetchShopCategoryList() -> AnyPublisher - func fetchShopData(requestModel: FetchShopDataRequest) -> AnyPublisher - func fetchShopMenuList(requestModel: FetchShopDataRequest) -> AnyPublisher - func fetchShopEventList(requestModel: FetchShopDataRequest) -> AnyPublisher - func searchRelatedShops(text: String) -> AnyPublisher + func fetchShopMenusCategory(shopId: Int) -> AnyPublisher + func fetchShopSummary(id: Int) -> AnyPublisher + func fetchShopList(requestModel: FetchShopListRequest) -> AnyPublisher + func fetchEventList() -> AnyPublisher + func fetchShopCategoryList() -> AnyPublisher + func fetchShopData(requestModel: FetchShopDataRequest) -> AnyPublisher + func fetchShopMenuList(requestModel: FetchShopDataRequest) -> AnyPublisher + func fetchShopEventList(requestModel: FetchShopDataRequest) -> AnyPublisher + func searchRelatedShops(text: String) -> AnyPublisher - func fetchReviewList(requestModel: FetchShopReviewRequest, retry: Bool) -> AnyPublisher + func fetchReviewList(requestModel: FetchShopReviewRequest) -> AnyPublisher func fetchReview(reviewId: Int, shopId: Int) -> AnyPublisher func fetchMyReviewList(requestModel: FetchMyReviewRequest, shopId: Int) -> AnyPublisher func postReview(requestModel: WriteReviewRequest, shopId: Int) -> AnyPublisher func modifyReview(requestModel: WriteReviewRequest, reviewId: Int, shopId: Int) -> AnyPublisher func deleteReview(reviewId: Int, shopId: Int) -> AnyPublisher func reportReview(requestModel: ReportReviewRequest, reviewId: Int, shopId: Int) -> AnyPublisher - func fetchShopBenefits() -> AnyPublisher - func fetchBeneficialShops(id: Int) -> AnyPublisher + func fetchShopBenefits() -> AnyPublisher + func fetchBeneficialShops(id: Int) -> AnyPublisher func postCallNotification(shopId: Int) -> AnyPublisher func uploadFiles(files: [Data]) -> AnyPublisher - func fetchSearchShop(requestModel: FetchShopSearchRequest) -> AnyPublisher + func fetchSearchShop(requestModel: FetchShopSearchRequest) -> AnyPublisher } @@ -41,198 +41,87 @@ final class DefaultShopService: ShopService { private let networkService = NetworkService() - func fetchShopMenusCategory(shopId: Int) -> AnyPublisher { - request(.fetchShopMenusCategoryList(shopId: shopId)) + func fetchShopMenusCategory(shopId: Int) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchShopMenusCategoryList(shopId: shopId)) } - func fetchShopSummary(id: Int) -> AnyPublisher { - request(.fetchShopSummary(id)) - + func fetchShopSummary(id: Int) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchShopSummary(id)) } - func fetchShopBenefits() -> AnyPublisher { - request(.fetchShopBenefits) + func fetchShopBenefits() -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchShopBenefits) } - func fetchBeneficialShops(id: Int) -> AnyPublisher { - request(.fetchBeneficialShops(id)) + func fetchBeneficialShops(id: Int) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchBeneficialShops(id)) } func uploadFiles(files: [Data]) -> AnyPublisher { return networkService.uploadFiles(api: ShopAPI.uploadFiles(files)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.uploadFiles(api: ShopAPI.uploadFiles(files)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() - } - - func fetchReviewList(requestModel: FetchShopReviewRequest, retry: Bool) -> AnyPublisher { + } + + func fetchReviewList(requestModel: FetchShopReviewRequest) -> AnyPublisher { return networkService.requestWithResponse(api: ShopAPI.fetchReviewList(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" && !retry { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: ShopAPI.fetchReviewList(requestModel)) } - .catch { refreshError -> AnyPublisher in - return self.fetchReviewList(requestModel: requestModel, retry: true) - } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func fetchReview(reviewId: Int, shopId: Int) -> AnyPublisher { return networkService.requestWithResponse(api: ShopAPI.fetchReview(reviewId, shopId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: ShopAPI.fetchReview(reviewId, shopId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func fetchMyReviewList(requestModel: FetchMyReviewRequest, shopId: Int) -> AnyPublisher { return networkService.requestWithResponse(api: ShopAPI.fetchMyReviewList(requestModel, shopId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: ShopAPI.fetchMyReviewList(requestModel, shopId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func postReview(requestModel: WriteReviewRequest, shopId: Int) -> AnyPublisher { return networkService.request(api: ShopAPI.postReview(requestModel, shopId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: ShopAPI.postReview(requestModel, shopId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func modifyReview(requestModel: WriteReviewRequest, reviewId: Int, shopId: Int) -> AnyPublisher { return networkService.request(api: ShopAPI.modifyReview(requestModel, reviewId, shopId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: ShopAPI.modifyReview(requestModel, reviewId, shopId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func deleteReview(reviewId: Int, shopId: Int) -> AnyPublisher { return networkService.request(api: ShopAPI.deleteReview(reviewId, shopId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: ShopAPI.deleteReview(reviewId, shopId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func reportReview(requestModel: ReportReviewRequest, reviewId: Int, shopId: Int) -> AnyPublisher { return networkService.request(api: ShopAPI.reportReview(requestModel, reviewId, shopId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: ShopAPI.reportReview(requestModel, reviewId, shopId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - func fetchShopList(requestModel: FetchShopListRequest) -> AnyPublisher { - return request(.fetchShopList(requestModel)) + func fetchShopList(requestModel: FetchShopListRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchShopList(requestModel)) } - func fetchEventList() -> AnyPublisher { - return request(.fetchEventList) + func fetchEventList() -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchEventList) } - func fetchShopCategoryList() -> AnyPublisher { - return request(.fetchShopCategoryList) + func fetchShopCategoryList() -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchShopCategoryList) } - func fetchShopData(requestModel: FetchShopDataRequest) -> AnyPublisher { - return request(.fetchShopData(requestModel)) + func fetchShopData(requestModel: FetchShopDataRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchShopData(requestModel)) } - func fetchShopMenuList(requestModel: FetchShopDataRequest) -> AnyPublisher { - return request(.fetchShopMenuList(requestModel)) + func fetchShopMenuList(requestModel: FetchShopDataRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchShopMenuList(requestModel)) } - func fetchShopEventList(requestModel: FetchShopDataRequest) -> AnyPublisher { - return request(.fetchShopEventList(requestModel)) + func fetchShopEventList(requestModel: FetchShopDataRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchShopEventList(requestModel)) } - func searchRelatedShops(text: String) -> AnyPublisher { - return request(.searchShop(text)) + func searchRelatedShops(text: String) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.searchShop(text)) } - func fetchSearchShop(requestModel: FetchShopSearchRequest) -> AnyPublisher { - return request(.fetchSearchShop(requestModel)) + func fetchSearchShop(requestModel: FetchShopSearchRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: ShopAPI.fetchSearchShop(requestModel)) } func postCallNotification(shopId: Int) -> AnyPublisher { return networkService.request(api: ShopAPI.postCallNotification(shopId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: ShopAPI.postCallNotification(shopId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() - } - - private func request(_ api: ShopAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() } } diff --git a/Koin/Data/Service/TimetableService.swift b/Koin/Data/Service/TimetableService.swift index fb210a5a4..b099eaf0e 100644 --- a/Koin/Data/Service/TimetableService.swift +++ b/Koin/Data/Service/TimetableService.swift @@ -9,7 +9,7 @@ import Alamofire import Combine protocol TimetableService { - func fetchDeptList() -> AnyPublisher<[DeptDto], Error> + func fetchDeptList() -> AnyPublisher<[DeptDto], ErrorResponse> func fetchFrame(semester: String) -> AnyPublisher<[FrameDto], ErrorResponse> func deleteFrame(id: Int) -> AnyPublisher func createFrame(semester: String) -> AnyPublisher @@ -18,8 +18,8 @@ protocol TimetableService { func modifyLecture(request: LectureRequest) -> AnyPublisher func postLecture(request: LectureRequest) -> AnyPublisher func fetchMySemester() -> AnyPublisher - func fetchLectureList(semester: String) -> AnyPublisher<[SemesterLecture], Error> - func fetchSemester() -> AnyPublisher<[SemesterDto], Error> + func fetchLectureList(semester: String) -> AnyPublisher<[SemesterLecture], ErrorResponse> + func fetchSemester() -> AnyPublisher<[SemesterDto], ErrorResponse> func deleteLecture(frameId: Int, lectureId: Int) -> AnyPublisher func deleteSemester(semester: String) -> AnyPublisher func _deleteLecture(id: Int) -> AnyPublisher @@ -33,216 +33,64 @@ final class DefaultTimetableService: TimetableService { func fetchAllFrames() -> AnyPublisher { return networkService.requestWithResponse(api: TimetableAPI.fetchAllFrames) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.fetchAllFrames) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func rollbackFrame(id: Int) -> AnyPublisher { return networkService.requestWithResponse(api: TimetableAPI.rollbackFrame(id: id)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.rollbackFrame(id: id)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func _deleteLecture(id: Int) -> AnyPublisher { return networkService.request(api: TimetableAPI._deleteLecture(id: id)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: TimetableAPI._deleteLecture(id: id)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func deleteLecture(frameId: Int, lectureId: Int) -> AnyPublisher { return networkService.request(api: TimetableAPI.deleteLecture(frameId: frameId, lectureId: lectureId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: TimetableAPI.deleteLecture(frameId: frameId, lectureId: lectureId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func deleteSemester(semester: String) -> AnyPublisher { return networkService.request(api: TimetableAPI.deleteSemester(semester: semester)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: TimetableAPI.deleteSemester(semester: semester)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func fetchMySemester() -> AnyPublisher { return networkService.requestWithResponse(api: TimetableAPI.fetchMySemester) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.fetchMySemester) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - func fetchLectureList(semester: String) -> AnyPublisher<[SemesterLecture], Error> { - request(.fetchLectureList(semester: semester)) + func fetchLectureList(semester: String) -> AnyPublisher<[SemesterLecture], ErrorResponse> { + return networkService.requestWithResponse(api: TimetableAPI.fetchLectureList(semester: semester)) } - func fetchSemester() -> AnyPublisher<[SemesterDto], Error> { - request(.fetchSemester) + func fetchSemester() -> AnyPublisher<[SemesterDto], ErrorResponse> { + return networkService.requestWithResponse(api: TimetableAPI.fetchSemester) } - func fetchDeptList() -> AnyPublisher<[DeptDto], Error> { - return request(.fetchDeptList) + func fetchDeptList() -> AnyPublisher<[DeptDto], ErrorResponse> { + return networkService.requestWithResponse(api: TimetableAPI.fetchDeptList) } func fetchLecture(frameId: Int) -> AnyPublisher { return networkService.requestWithResponse(api: TimetableAPI.fetchLecture(frameId: frameId)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.fetchLecture(frameId: frameId)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func modifyLecture(request: LectureRequest) -> AnyPublisher { return networkService.requestWithResponse(api: TimetableAPI.modifyLecture(request: request)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.modifyLecture(request: request)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func postLecture(request: LectureRequest) -> AnyPublisher { return networkService.requestWithResponse(api: TimetableAPI.postLecture(request: request)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.postLecture(request: request)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func fetchFrame(semester: String) -> AnyPublisher<[FrameDto], ErrorResponse> { return networkService.requestWithResponse(api: TimetableAPI.fetchFrame(semester: semester)) - .catch { [weak self] error -> AnyPublisher<[FrameDto], ErrorResponse> in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.fetchFrame(semester: semester)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func createFrame(semester: String) -> AnyPublisher { return networkService.requestWithResponse(api: TimetableAPI.createFrame(semester: semester)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.createFrame(semester: semester)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func modifyFrame(frame: FrameDto) -> AnyPublisher { return networkService.requestWithResponse(api: TimetableAPI.modifyFrame(frame: frame)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: TimetableAPI.modifyFrame(frame: frame)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func deleteFrame(id: Int) -> AnyPublisher { return networkService.request(api: TimetableAPI.deleteFrame(id: id)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: TimetableAPI.deleteFrame(id: id)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() - } - - - private func request(_ api: TimetableAPI) -> AnyPublisher { - return AF.request(api) - .publishDecodable(type: T.self) - .value() - .mapError { $0 as Error } - .eraseToAnyPublisher() } } diff --git a/Koin/Data/Service/UserService.swift b/Koin/Data/Service/UserService.swift index ef3cfd9de..04ea39317 100644 --- a/Koin/Data/Service/UserService.swift +++ b/Koin/Data/Service/UserService.swift @@ -35,148 +35,46 @@ protocol UserService { } final class DefaultUserService: UserService { + + private let networkService = NetworkService() + func changePassword(requestModel: ChangePasswordRequest) -> AnyPublisher { return networkService.request(api: UserAPI.changePassword(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: UserAPI.changePassword(requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } - - private let networkService = NetworkService() - func resetPasswordSms(requestModel: ResetPasswordSmsRequest) -> AnyPublisher { return networkService.request(api: UserAPI.resetPasswordSms(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: UserAPI.resetPasswordSms(requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func resetPasswordEmail(requestModel: ResetPasswordEmailRequest) -> AnyPublisher { return networkService.request(api: UserAPI.resetPasswordEmail(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: UserAPI.resetPasswordEmail(requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func findIdSms(requestModel: FindIdSmsRequest) -> AnyPublisher { return networkService.requestWithResponse(api: UserAPI.findIdSms(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: UserAPI.findIdSms(requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func findIdEmail(requestModel: FindIdEmailRequest) -> AnyPublisher { return networkService.requestWithResponse(api: UserAPI.findIdEmail(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: UserAPI.findIdEmail(requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func sendVerificationEmail(requestModel: SendVerificationEmailRequest) -> AnyPublisher { return networkService.request(api: UserAPI.sendVerificationEmail(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: UserAPI.sendVerificationEmail(requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func checkVerificationEmail(requestModel: CheckVerificationEmailRequest) -> AnyPublisher { return networkService.request(api: UserAPI.checkVerificationEmail(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: UserAPI.checkVerificationEmail(requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func checkLogin() -> AnyPublisher { - networkService.request(api: UserAPI.checkLogin) + return (networkService.request(api: UserAPI.checkLogin) as AnyPublisher) .map { _ in true } - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { - return Just(false).eraseToAnyPublisher() - } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: UserAPI.checkLogin) } - .map { _ in true } - .replaceError(with: false) - .eraseToAnyPublisher() - } else { - return Just(false).eraseToAnyPublisher() - } - } .replaceError(with: false) .eraseToAnyPublisher() } func checkAuth() -> AnyPublisher { return networkService.requestWithResponse(api: UserAPI.checkAuth) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: UserAPI.checkAuth) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func findPassword(requestModel: FindPasswordRequest) -> AnyPublisher { @@ -200,94 +98,32 @@ final class DefaultUserService: UserService { } func fetchUserData() -> AnyPublisher { - return networkService.requestWithResponse(api: UserAPI.checkAuth) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: UserAPI.checkAuth) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } + return (networkService.requestWithResponse(api: UserAPI.checkAuth) as AnyPublisher) .flatMap { [weak self] userTypeResponse -> AnyPublisher in guard let self = self else { return Empty().eraseToAnyPublisher() } let userType = userTypeResponse.userType let api: UserAPI = userType == .student ? .fetchStudentUserData : .fetchGeneralUserData return self.networkService.requestWithResponse(api: api) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: api) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - }.eraseToAnyPublisher() }.eraseToAnyPublisher() } func revoke() -> AnyPublisher { return networkService.request(api: UserAPI.revoke) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: UserAPI.revoke) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func modify(requestModel: UserPutRequest) -> AnyPublisher { - return networkService.requestWithResponse(api: UserAPI.checkAuth) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: UserAPI.checkAuth) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } + return (networkService.requestWithResponse(api: UserAPI.checkAuth) as AnyPublisher) .flatMap { [weak self] userTypeResponse -> AnyPublisher in guard let self = self else { return Empty().eraseToAnyPublisher() } let userType = userTypeResponse.userType let api: UserAPI = userType == .student ? .modifyStudentUserData(requestModel) : .modifyGeneralUserData(requestModel) return self.networkService.requestWithResponse(api: api) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.requestWithResponse(api: api) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - }.eraseToAnyPublisher() } .eraseToAnyPublisher() } func checkPassword(requestModel: CheckPasswordRequest) -> AnyPublisher { return networkService.request(api: UserAPI.checkPassword(requestModel)) - .catch { [weak self] error -> AnyPublisher in - guard let self = self else { return Fail(error: error).eraseToAnyPublisher() } - if error.code == "401" { - return self.networkService.refreshToken() - .flatMap { _ in self.networkService.request(api: UserAPI.checkPassword(requestModel)) } - .eraseToAnyPublisher() - } else { - return Fail(error: error).eraseToAnyPublisher() - } - } - .eraseToAnyPublisher() } func sendVerificationCode(requestModel: SendVerificationCodeRequest) -> AnyPublisher { diff --git a/Koin/Domain/Model/ErrorResponse.swift b/Koin/Domain/Model/ErrorResponse.swift new file mode 100644 index 000000000..02ec10f8f --- /dev/null +++ b/Koin/Domain/Model/ErrorResponse.swift @@ -0,0 +1,44 @@ +// +// ErrorResponse.swift +// koin +// +// Created by 홍기정 on 2/15/26. +// + +import Foundation + +struct ErrorResponse: Error { + var statusCode: Int? = nil + let code: String + let message: String + + init(statusCode: Int? = nil, code: String, message: String) { + self.statusCode = statusCode + self.code = code + self.message = message + } + + init(_ userInputError: UserInputError) { + self.code = "" + self.message = userInputError.errorDescription ?? "" + } +} + +extension ErrorResponse { + + static func decodingError(_ statusCode: Int) -> Self { + return ErrorResponse(statusCode: statusCode, code: "DECODING_ERROR", message: "디코딩 실패") + } + static func emptyDataError(_ statusCode: Int) -> Self { + return ErrorResponse(statusCode: statusCode, code: "EMPTY_DATA_ERROR", message: "응답 데이터가 없습니다") + } + + static let unexpectedInternalError = ErrorResponse(statusCode: nil, code: "UNEXPECTED_INTERNAL_ERROR", message: "내부 오류") + static let typeCastingError = ErrorResponse(statusCode: nil, code: "TYPE_CASTING_ERROR", message: "타입 캐스팅 실패") + static let networkError = ErrorResponse(statusCode: nil, code: "NETWORK_ERROR", message: "서버 응답 오류") + static let invalidUrl = ErrorResponse(statusCode: nil, code: "INVALID_URL", message: "URL이 유효하지 않습니다.") + static let invalidApi = ErrorResponse(statusCode: nil, code: "INVALID_API", message: "API가 유효하지 않습니다.") + static let fileManagerFailedDirectory = ErrorResponse(statusCode: nil, code: "FILEMANAGER_FAILED_DIRECTORY", message: "파일 저장 위치 찾기 실패") + static let deleteKeywordError = ErrorResponse(statusCode: nil, code: "DELETE_KEYWORD_ERROR", message: "로그인에 실패하여 코어데이터에서 키워드 삭제") + static let createKeywordError = ErrorResponse(statusCode: nil, code: "CREATE_KEYWORD_ERROR", message: "로그인에 실패하여 코어데이터에서 키워드 저장") +} diff --git a/Koin/Domain/Repository/BusRepository.swift b/Koin/Domain/Repository/BusRepository.swift index ae9a0c981..c6eca8df7 100644 --- a/Koin/Domain/Repository/BusRepository.swift +++ b/Koin/Domain/Repository/BusRepository.swift @@ -8,10 +8,10 @@ import Combine protocol BusRepository { - func searchBusInformation(requestModel: SearchBusInfoRequest) -> AnyPublisher - func fetchExpressBusTimetableList(requestModel: FetchBusTimetableRequest) -> AnyPublisher - func fetchShuttleRouteList() -> AnyPublisher - func fetchCityBusTimetableList(requestModel: FetchCityBusTimetableRequest) -> AnyPublisher - func fetchEmergencyNotice() -> AnyPublisher - func fetchShuttleBusTimetable(id: String) -> AnyPublisher + func searchBusInformation(requestModel: SearchBusInfoRequest) -> AnyPublisher + func fetchExpressBusTimetableList(requestModel: FetchBusTimetableRequest) -> AnyPublisher + func fetchShuttleRouteList() -> AnyPublisher + func fetchCityBusTimetableList(requestModel: FetchCityBusTimetableRequest) -> AnyPublisher + func fetchEmergencyNotice() -> AnyPublisher + func fetchShuttleBusTimetable(id: String) -> AnyPublisher } diff --git a/Koin/Domain/Repository/CoreRepository.swift b/Koin/Domain/Repository/CoreRepository.swift index 5b9e3b581..302cf557e 100644 --- a/Koin/Domain/Repository/CoreRepository.swift +++ b/Koin/Domain/Repository/CoreRepository.swift @@ -8,8 +8,8 @@ import Combine protocol CoreRepository { - func fetchVersion() -> AnyPublisher - func fetBanner() -> AnyPublisher - func fetchClubCategories() -> AnyPublisher - func fetchHotClubs() -> AnyPublisher + func fetchVersion() -> AnyPublisher + func fetBanner() -> AnyPublisher + func fetchClubCategories() -> AnyPublisher + func fetchHotClubs() -> AnyPublisher } diff --git a/Koin/Domain/Repository/DiningRepository.swift b/Koin/Domain/Repository/DiningRepository.swift index e17ca6751..81e6dd847 100644 --- a/Koin/Domain/Repository/DiningRepository.swift +++ b/Koin/Domain/Repository/DiningRepository.swift @@ -9,7 +9,7 @@ import Combine protocol DiningRepository { func fetchDiningList(requestModel: FetchDiningListRequest) -> AnyPublisher<[DiningDto], ErrorResponse> - func fetchCoopShopList() -> AnyPublisher + func fetchCoopShopList() -> AnyPublisher func diningLike(requestModel: DiningLikeRequest, isLiked: Bool) -> AnyPublisher func shareMenuList(shareModel: ShareDiningMenu) } diff --git a/Koin/Domain/Repository/LandRepository.swift b/Koin/Domain/Repository/LandRepository.swift index 4dc2019be..7ccb2aa41 100644 --- a/Koin/Domain/Repository/LandRepository.swift +++ b/Koin/Domain/Repository/LandRepository.swift @@ -8,6 +8,6 @@ import Combine protocol LandRepository { - func fetchLandList() -> AnyPublisher - func fetchLandDetail(requestModel: FetchLandDetailRequest) -> AnyPublisher + func fetchLandList() -> AnyPublisher + func fetchLandDetail(requestModel: FetchLandDetailRequest) -> AnyPublisher } diff --git a/Koin/Domain/Repository/LostItemRepository.swift b/Koin/Domain/Repository/LostItemRepository.swift index bbf04ad51..1b7092181 100644 --- a/Koin/Domain/Repository/LostItemRepository.swift +++ b/Koin/Domain/Repository/LostItemRepository.swift @@ -9,10 +9,10 @@ import Foundation import Combine protocol LostItemRepository { - func fetchLostItemList(requestModel: FetchLostItemListRequest) -> AnyPublisher - func fetchLostItemData(id: Int) -> AnyPublisher + func fetchLostItemList(requestModel: FetchLostItemListRequest) -> AnyPublisher + func fetchLostItemData(id: Int) -> AnyPublisher func changeLostItemState(id: Int) -> AnyPublisher - func deleteLostItem(id: Int) -> AnyPublisher + func deleteLostItem(id: Int) -> AnyPublisher func updateLostItem(id: Int, requestModel: UpdateLostItemRequest) -> AnyPublisher - func fetchLostItemStats() -> AnyPublisher + func fetchLostItemStats() -> AnyPublisher } diff --git a/Koin/Domain/Repository/NoticeListRepository.swift b/Koin/Domain/Repository/NoticeListRepository.swift index fb36f658c..2f38c0f18 100644 --- a/Koin/Domain/Repository/NoticeListRepository.swift +++ b/Koin/Domain/Repository/NoticeListRepository.swift @@ -9,15 +9,15 @@ import Combine import Foundation protocol NoticeListRepository { - func fetchNoticeArticles(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher - func searchNoticeArticle(requestModel: SearchNoticeArticleRequest) -> AnyPublisher - func fetchLostItemArticles(requestModel: FetchLostItemsRequest) -> AnyPublisher - func fetchNoticeData(requestModel: FetchNoticeDataRequest) -> AnyPublisher - func fetchHotNoticeArticle() -> AnyPublisher<[NoticeArticleDto], Error> + func fetchNoticeArticles(requestModel: FetchNoticeArticlesRequest) -> AnyPublisher + func searchNoticeArticle(requestModel: SearchNoticeArticleRequest) -> AnyPublisher + func fetchLostItemArticles(requestModel: FetchLostItemsRequest) -> AnyPublisher + func fetchNoticeData(requestModel: FetchNoticeDataRequest) -> AnyPublisher + func fetchHotNoticeArticle() -> AnyPublisher<[NoticeArticleDto], ErrorResponse> func createNotificationKeyword(requestModel: NoticeKeywordDto) -> AnyPublisher func deleteNotificationKeyword(requestModel: NoticeKeywordDto) -> AnyPublisher func fetchNotificationKeyword() -> AnyPublisher - func fetchRecommendedKeyword(count: Int?) -> AnyPublisher + func fetchRecommendedKeyword(count: Int?) -> AnyPublisher func downloadNoticeAttachment(downloadUrl: String, fileName: String) -> AnyPublisher func manageRecentSearchedWord(name: String, date: Date, actionType: Int) func fetchRecentSearchedWord() -> [RecentSearchedWordInfo] diff --git a/Koin/Domain/Repository/ShopRepository.swift b/Koin/Domain/Repository/ShopRepository.swift index 16bf5e731..282777e28 100644 --- a/Koin/Domain/Repository/ShopRepository.swift +++ b/Koin/Domain/Repository/ShopRepository.swift @@ -9,15 +9,15 @@ import Combine import Foundation protocol ShopRepository { - func fetchShopSummary(id: Int) -> AnyPublisher - func fetchShopList(requestModel: FetchShopListRequest) -> AnyPublisher - func fetchEventList() -> AnyPublisher - func fetchShopCategoryList() -> AnyPublisher - func fetchShopMenusCategoryList(shopId: Int) -> AnyPublisher + func fetchShopSummary(id: Int) -> AnyPublisher + func fetchShopList(requestModel: FetchShopListRequest) -> AnyPublisher + func fetchEventList() -> AnyPublisher + func fetchShopCategoryList() -> AnyPublisher + func fetchShopMenusCategoryList(shopId: Int) -> AnyPublisher - func fetchShopData(requestModel: FetchShopDataRequest) -> AnyPublisher - func fetchShopMenuList(requestModel: FetchShopDataRequest) -> AnyPublisher - func fetchShopEventList(requestModel: FetchShopDataRequest) -> AnyPublisher + func fetchShopData(requestModel: FetchShopDataRequest) -> AnyPublisher + func fetchShopMenuList(requestModel: FetchShopDataRequest) -> AnyPublisher + func fetchShopEventList(requestModel: FetchShopDataRequest) -> AnyPublisher func fetchReviewList(requestModel: FetchShopReviewRequest) -> AnyPublisher func fetchReview(reviewId: Int, shopId: Int) -> AnyPublisher @@ -26,12 +26,12 @@ protocol ShopRepository { func modifyReview(requestModel: WriteReviewRequest, reviewId: Int, shopId: Int) -> AnyPublisher func deleteReview(reviewId: Int, shopId: Int) -> AnyPublisher func reportReview(requestModel: ReportReviewRequest, reviewId: Int, shopId: Int) -> AnyPublisher - func fetchShopBenefits() -> AnyPublisher - func fetchBeneficialShops(id: Int) -> AnyPublisher - func searchRelatedQuery(text: String) -> AnyPublisher + func fetchShopBenefits() -> AnyPublisher + func fetchBeneficialShops(id: Int) -> AnyPublisher + func searchRelatedQuery(text: String) -> AnyPublisher func uploadFiles(files: [Data]) -> AnyPublisher func postCallNotification(shopId: Int) -> AnyPublisher - func fetchSearchShop(requestModel: FetchShopSearchRequest) -> AnyPublisher + func fetchSearchShop(requestModel: FetchShopSearchRequest) -> AnyPublisher } diff --git a/Koin/Domain/Repository/TimetableRepository.swift b/Koin/Domain/Repository/TimetableRepository.swift index 56a9011ca..ac8eddec8 100644 --- a/Koin/Domain/Repository/TimetableRepository.swift +++ b/Koin/Domain/Repository/TimetableRepository.swift @@ -8,7 +8,7 @@ import Combine protocol TimetableRepository { - func fetchDeptList() -> AnyPublisher<[DeptDto], Error> + func fetchDeptList() -> AnyPublisher<[DeptDto], ErrorResponse> func fetchFrame(semester: String) -> AnyPublisher<[FrameDto], ErrorResponse> func deleteFrame(id: Int) -> AnyPublisher func createFrame(semester: String) -> AnyPublisher @@ -17,8 +17,8 @@ protocol TimetableRepository { func modifyLecture(request: LectureRequest) -> AnyPublisher func postLecture(request: LectureRequest) -> AnyPublisher func fetchMySemester() -> AnyPublisher - func fetchLectureList(semester: String) -> AnyPublisher<[SemesterLecture], Error> - func fetchSemester() -> AnyPublisher<[SemesterDto], Error> + func fetchLectureList(semester: String) -> AnyPublisher<[SemesterLecture], ErrorResponse> + func fetchSemester() -> AnyPublisher<[SemesterDto], ErrorResponse> func deleteLecture(frameId: Int, lectureId: Int) -> AnyPublisher func deleteSemester(semester: String) -> AnyPublisher func deleteLecture(id: Int) -> AnyPublisher diff --git a/Koin/Domain/Repository/UserRepository.swift b/Koin/Domain/Repository/UserRepository.swift index ffc58febe..871656779 100644 --- a/Koin/Domain/Repository/UserRepository.swift +++ b/Koin/Domain/Repository/UserRepository.swift @@ -12,11 +12,9 @@ protocol UserRepository { func modify(requestModel: UserPutRequest) -> AnyPublisher func register(requestModel: UserRegisterRequest) -> AnyPublisher func login(requestModel: LoginRequest) -> AnyPublisher - func logout() -> AnyPublisher func findPassword(requestModel: FindPasswordRequest) -> AnyPublisher func checkDuplicatedPhoneNumber(requestModel: CheckDuplicatedPhoneNumberRequest) -> AnyPublisher func checkDuplicatedNickname(requestModel: CheckDuplicatedNicknameRequest) -> AnyPublisher - func checkDuplicatedEmail(email: String) -> AnyPublisher func revoke() -> AnyPublisher func checkPassword(requestModel: CheckPasswordRequest) -> AnyPublisher func checkAuth() -> AnyPublisher diff --git a/Koin/Domain/UseCase/Bus/FetchCityBusTimetableUseCase.swift b/Koin/Domain/UseCase/Bus/FetchCityBusTimetableUseCase.swift index 5be6a7201..fafa4b963 100644 --- a/Koin/Domain/UseCase/Bus/FetchCityBusTimetableUseCase.swift +++ b/Koin/Domain/UseCase/Bus/FetchCityBusTimetableUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchCityBusTimetableUseCase { - func execute(firstFilterIdx: Int, secondFilterIdx: Int) -> AnyPublisher<(BusTimetableInfo, String), Error> + func execute(firstFilterIdx: Int, secondFilterIdx: Int) -> AnyPublisher<(BusTimetableInfo, String), ErrorResponse> } final class DefaultFetchCityBusTimetableUseCase: FetchCityBusTimetableUseCase { @@ -18,7 +18,7 @@ final class DefaultFetchCityBusTimetableUseCase: FetchCityBusTimetableUseCase { self.busRepository = busRepository } - func execute(firstFilterIdx: Int, secondFilterIdx: Int) -> AnyPublisher<(BusTimetableInfo, String), Error> { + func execute(firstFilterIdx: Int, secondFilterIdx: Int) -> AnyPublisher<(BusTimetableInfo, String), ErrorResponse> { var busCourses: [CityBusCourseInfo] = [] if firstFilterIdx == 0 { busCourses = setToCityBusCourses() diff --git a/Koin/Domain/UseCase/Bus/FetchEmergencyNoticeUseCase.swift b/Koin/Domain/UseCase/Bus/FetchEmergencyNoticeUseCase.swift index 770ff380f..bc1f94225 100644 --- a/Koin/Domain/UseCase/Bus/FetchEmergencyNoticeUseCase.swift +++ b/Koin/Domain/UseCase/Bus/FetchEmergencyNoticeUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchEmergencyNoticeUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchEmergencyNoticeUseCase: FetchEmergencyNoticeUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchEmergencyNoticeUseCase: FetchEmergencyNoticeUseCase { self.repository = repository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return repository.fetchEmergencyNotice().eraseToAnyPublisher() } } diff --git a/Koin/Domain/UseCase/Bus/FetchExpressTimetableUseCase.swift b/Koin/Domain/UseCase/Bus/FetchExpressTimetableUseCase.swift index a6870b698..f4e6d2767 100644 --- a/Koin/Domain/UseCase/Bus/FetchExpressTimetableUseCase.swift +++ b/Koin/Domain/UseCase/Bus/FetchExpressTimetableUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchExpressTimetableUseCase { - func execute(filterIdx: Int) -> AnyPublisher<(BusTimetableInfo, BusDirection), Error> + func execute(filterIdx: Int) -> AnyPublisher<(BusTimetableInfo, BusDirection), ErrorResponse> } final class DefaultFetchExpressTimetableUseCase: FetchExpressTimetableUseCase { @@ -18,7 +18,7 @@ final class DefaultFetchExpressTimetableUseCase: FetchExpressTimetableUseCase { self.busRepository = busRepository } - func execute(filterIdx: Int) -> AnyPublisher<(BusTimetableInfo, BusDirection), Error> { + func execute(filterIdx: Int) -> AnyPublisher<(BusTimetableInfo, BusDirection), ErrorResponse> { let busCourse = setBusFilter()[filterIdx] let busDirection: BusDirection = filterIdx == 0 ? .from : .to let requestModel = FetchBusTimetableRequest(busType: busCourse.busType.rawValue, direction: busCourse.direction.rawValue, region: busCourse.region) diff --git a/Koin/Domain/UseCase/Bus/FetchShuttleBusRoutesUseCase.swift b/Koin/Domain/UseCase/Bus/FetchShuttleBusRoutesUseCase.swift index f39e35a02..52ae63498 100644 --- a/Koin/Domain/UseCase/Bus/FetchShuttleBusRoutesUseCase.swift +++ b/Koin/Domain/UseCase/Bus/FetchShuttleBusRoutesUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchShuttleBusRoutesUseCase { - func execute(busRouteType: ShuttleRouteType) -> AnyPublisher + func execute(busRouteType: ShuttleRouteType) -> AnyPublisher } final class DefaultFetchShuttleBusRoutesUseCase: FetchShuttleBusRoutesUseCase { @@ -18,7 +18,7 @@ final class DefaultFetchShuttleBusRoutesUseCase: FetchShuttleBusRoutesUseCase { self.busRepository = busRepository } - func execute(busRouteType: ShuttleRouteType) -> AnyPublisher { + func execute(busRouteType: ShuttleRouteType) -> AnyPublisher { return busRepository.fetchShuttleRouteList().map { [weak self] routeList in return self?.filterByShuttleRouteType(busTimetableInfo: routeList, shuttleRouteType: busRouteType) ?? ShuttleRouteDto(routeRegions: [], semesterInfo: SemesterInfo(name: "", from: "", to: "")) }.eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/Bus/FetchShuttleBusTimetableUseCase.swift b/Koin/Domain/UseCase/Bus/FetchShuttleBusTimetableUseCase.swift index 0d0f15ea8..bea09d988 100644 --- a/Koin/Domain/UseCase/Bus/FetchShuttleBusTimetableUseCase.swift +++ b/Koin/Domain/UseCase/Bus/FetchShuttleBusTimetableUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchShuttleBusTimetableUseCase { - func execute(id: String) -> AnyPublisher + func execute(id: String) -> AnyPublisher } final class DefaultFetchShuttleBusTimetableUseCase: FetchShuttleBusTimetableUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchShuttleBusTimetableUseCase: FetchShuttleBusTimetableUseC self.repository = repository } - func execute(id: String) -> AnyPublisher { + func execute(id: String) -> AnyPublisher { return repository.fetchShuttleBusTimetable(id: id) } } diff --git a/Koin/Domain/UseCase/Bus/SearchBusInfoUseCase.swift b/Koin/Domain/UseCase/Bus/SearchBusInfoUseCase.swift index f4b55ab41..30094fcf9 100644 --- a/Koin/Domain/UseCase/Bus/SearchBusInfoUseCase.swift +++ b/Koin/Domain/UseCase/Bus/SearchBusInfoUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol SearchBusInfoUseCase { - func execute(date: String, busType: BusType, departure: BusPlace, arrival: BusPlace) -> AnyPublisher + func execute(date: String, busType: BusType, departure: BusPlace, arrival: BusPlace) -> AnyPublisher } final class DefaultSearchBusInfoUseCase: SearchBusInfoUseCase { @@ -19,7 +19,7 @@ final class DefaultSearchBusInfoUseCase: SearchBusInfoUseCase { self.busRepository = busRepository } - func execute(date: String, busType: BusType, departure: BusPlace, arrival: BusPlace) -> AnyPublisher { + func execute(date: String, busType: BusType, departure: BusPlace, arrival: BusPlace) -> AnyPublisher { let (departDate, departTime): (String, String) = self.processDate(date) let request = SearchBusInfoRequest( date: departDate, diff --git a/Koin/Domain/UseCase/Core/CheckVersionUseCase.swift b/Koin/Domain/UseCase/Core/CheckVersionUseCase.swift index 330daa6c1..a6f61ac9b 100644 --- a/Koin/Domain/UseCase/Core/CheckVersionUseCase.swift +++ b/Koin/Domain/UseCase/Core/CheckVersionUseCase.swift @@ -10,7 +10,7 @@ import Foundation protocol CheckVersionUseCase { - func execute() -> AnyPublisher<(Bool, String), Error> + func execute() -> AnyPublisher<(Bool, String), ErrorResponse> } final class DefaultCheckVersionUseCase: CheckVersionUseCase { @@ -21,7 +21,7 @@ final class DefaultCheckVersionUseCase: CheckVersionUseCase { self.coreRepository = coreRepository } - func execute() -> AnyPublisher<(Bool, String), Error> { + func execute() -> AnyPublisher<(Bool, String), ErrorResponse> { return coreRepository.fetchVersion() .map { response in let currentVersion = self.checkNowVersion() // 현재 버전 확인 diff --git a/Koin/Domain/UseCase/Core/FetchBannerUseCase.swift b/Koin/Domain/UseCase/Core/FetchBannerUseCase.swift index c8e23f93a..2022975ad 100644 --- a/Koin/Domain/UseCase/Core/FetchBannerUseCase.swift +++ b/Koin/Domain/UseCase/Core/FetchBannerUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchBannerUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchBannerUseCase: FetchBannerUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchBannerUseCase: FetchBannerUseCase { self.coreRepository = coreRepository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return coreRepository.fetBanner() } } diff --git a/Koin/Domain/UseCase/Core/FetchClubCategoriesUseCase.swift b/Koin/Domain/UseCase/Core/FetchClubCategoriesUseCase.swift index 0284d249e..d713cb7f2 100644 --- a/Koin/Domain/UseCase/Core/FetchClubCategoriesUseCase.swift +++ b/Koin/Domain/UseCase/Core/FetchClubCategoriesUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchClubCategoriesUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchClubCategoriesUseCase: FetchClubCategoriesUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchClubCategoriesUseCase: FetchClubCategoriesUseCase { self.coreRepository = coreRepository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return coreRepository.fetchClubCategories() } } diff --git a/Koin/Domain/UseCase/Core/FetchHotClubsUseCase.swift b/Koin/Domain/UseCase/Core/FetchHotClubsUseCase.swift index 865f0c7d6..b94c7352e 100644 --- a/Koin/Domain/UseCase/Core/FetchHotClubsUseCase.swift +++ b/Koin/Domain/UseCase/Core/FetchHotClubsUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchHotClubsUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchHotClubsUseCase: FetchHotClubsUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchHotClubsUseCase: FetchHotClubsUseCase { self.coreRepository = coreRepository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return coreRepository.fetchHotClubs() } } diff --git a/Koin/Domain/UseCase/Dining/FetchCoopShopListUseCase.swift b/Koin/Domain/UseCase/Dining/FetchCoopShopListUseCase.swift index 02bf2e1bd..2c9735cd4 100644 --- a/Koin/Domain/UseCase/Dining/FetchCoopShopListUseCase.swift +++ b/Koin/Domain/UseCase/Dining/FetchCoopShopListUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchCoopShopListUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchCoopShopListUseCase: FetchCoopShopListUseCase { @@ -20,9 +20,9 @@ final class DefaultFetchCoopShopListUseCase: FetchCoopShopListUseCase { self.diningRepository = diningRepository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return diningRepository.fetchCoopShopList() - .tryMap { coopShopDtos -> CoopShopData in + .map { coopShopDtos -> CoopShopData in return coopShopDtos.toDomain() } .eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/Land/FetchLandDetailUseCase.swift b/Koin/Domain/UseCase/Land/FetchLandDetailUseCase.swift index 0872d877f..d26ec2d59 100644 --- a/Koin/Domain/UseCase/Land/FetchLandDetailUseCase.swift +++ b/Koin/Domain/UseCase/Land/FetchLandDetailUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchLandDetailUseCase { - func execute(landId: Int) -> AnyPublisher + func execute(landId: Int) -> AnyPublisher } final class DefaultFetchLandDetailUseCase: FetchLandDetailUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchLandDetailUseCase: FetchLandDetailUseCase { self.landRepository = landRepository } - func execute(landId: Int) -> AnyPublisher { + func execute(landId: Int) -> AnyPublisher { return landRepository.fetchLandDetail(requestModel: FetchLandDetailRequest(id: landId)).map { landDetailDto in landDetailDto.toDomain() }.eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/Land/FetchLandListUseCase.swift b/Koin/Domain/UseCase/Land/FetchLandListUseCase.swift index 98795eac6..1800c6764 100644 --- a/Koin/Domain/UseCase/Land/FetchLandListUseCase.swift +++ b/Koin/Domain/UseCase/Land/FetchLandListUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchLandListUseCase { - func execute() -> AnyPublisher<[LandItem], Error> + func execute() -> AnyPublisher<[LandItem], ErrorResponse> } final class DefaultFetchLandListUseCase: FetchLandListUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchLandListUseCase: FetchLandListUseCase { self.landRepository = landRepository } - func execute() -> AnyPublisher<[LandItem], Error> { + func execute() -> AnyPublisher<[LandItem], ErrorResponse> { return landRepository.fetchLandList().map { landDto in landDto.lands?.map { $0.toDomain() } ?? [] }.eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/LostItem/DeleteLostItemUseCase.swift b/Koin/Domain/UseCase/LostItem/DeleteLostItemUseCase.swift index f6b43d9e9..1bf1c1813 100644 --- a/Koin/Domain/UseCase/LostItem/DeleteLostItemUseCase.swift +++ b/Koin/Domain/UseCase/LostItem/DeleteLostItemUseCase.swift @@ -9,7 +9,7 @@ import Foundation import Combine protocol DeleteLostItemUseCase { - func execute(id: Int) -> AnyPublisher + func execute(id: Int) -> AnyPublisher } final class DefaultDeleteLostItemUseCase: DeleteLostItemUseCase { @@ -20,7 +20,7 @@ final class DefaultDeleteLostItemUseCase: DeleteLostItemUseCase { self.repository = repository } - func execute(id: Int) -> AnyPublisher { + func execute(id: Int) -> AnyPublisher { repository.deleteLostItem(id: id) } } diff --git a/Koin/Domain/UseCase/LostItem/FetchLostItemDataUseCase.swift b/Koin/Domain/UseCase/LostItem/FetchLostItemDataUseCase.swift index d9334e248..b1aaa0aa4 100644 --- a/Koin/Domain/UseCase/LostItem/FetchLostItemDataUseCase.swift +++ b/Koin/Domain/UseCase/LostItem/FetchLostItemDataUseCase.swift @@ -9,7 +9,7 @@ import Foundation import Combine protocol FetchLostItemDataUseCase { - func execute(id: Int) -> AnyPublisher + func execute(id: Int) -> AnyPublisher } final class DefaultFetchLostItemDataUseCase: FetchLostItemDataUseCase { @@ -20,7 +20,7 @@ final class DefaultFetchLostItemDataUseCase: FetchLostItemDataUseCase { self.repository = repository } - func execute(id: Int) -> AnyPublisher { + func execute(id: Int) -> AnyPublisher { return repository.fetchLostItemData(id: id) } } diff --git a/Koin/Domain/UseCase/LostItem/FetchLostItemListUseCase.swift b/Koin/Domain/UseCase/LostItem/FetchLostItemListUseCase.swift index 2e69e4e70..516a0a014 100644 --- a/Koin/Domain/UseCase/LostItem/FetchLostItemListUseCase.swift +++ b/Koin/Domain/UseCase/LostItem/FetchLostItemListUseCase.swift @@ -9,7 +9,7 @@ import Foundation import Combine protocol FetchLostItemListUseCase { - func execute(requestModel: FetchLostItemListRequest) -> AnyPublisher + func execute(requestModel: FetchLostItemListRequest) -> AnyPublisher } final class DefaultFetchLostItemListUseCase: FetchLostItemListUseCase { @@ -20,7 +20,7 @@ final class DefaultFetchLostItemListUseCase: FetchLostItemListUseCase { self.repository = repository } - func execute(requestModel: FetchLostItemListRequest) -> AnyPublisher { + func execute(requestModel: FetchLostItemListRequest) -> AnyPublisher { return repository.fetchLostItemList(requestModel: requestModel) } } diff --git a/Koin/Domain/UseCase/LostItem/FetchLostItemStats.swift b/Koin/Domain/UseCase/LostItem/FetchLostItemStats.swift index d763377a4..bd95174df 100644 --- a/Koin/Domain/UseCase/LostItem/FetchLostItemStats.swift +++ b/Koin/Domain/UseCase/LostItem/FetchLostItemStats.swift @@ -9,7 +9,7 @@ import Foundation import Combine protocol FetchLostItemStatsUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchLostItemStatsUseCase: FetchLostItemStatsUseCase { @@ -20,7 +20,7 @@ final class DefaultFetchLostItemStatsUseCase: FetchLostItemStatsUseCase { self.repository = repository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return repository.fetchLostItemStats() } } diff --git a/Koin/Domain/UseCase/NoticeList/FetchHotNoticeArticlesUseCase.swift b/Koin/Domain/UseCase/NoticeList/FetchHotNoticeArticlesUseCase.swift index 455e92018..ac58a0176 100644 --- a/Koin/Domain/UseCase/NoticeList/FetchHotNoticeArticlesUseCase.swift +++ b/Koin/Domain/UseCase/NoticeList/FetchHotNoticeArticlesUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchHotNoticeArticlesUseCase { - func execute(noticeId: Int?) -> AnyPublisher<[NoticeArticleDto], Error> + func execute(noticeId: Int?) -> AnyPublisher<[NoticeArticleDto], ErrorResponse> } final class DefaultFetchHotNoticeArticlesUseCase: FetchHotNoticeArticlesUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchHotNoticeArticlesUseCase: FetchHotNoticeArticlesUseCase self.noticeListRepository = noticeListRepository } - func execute(noticeId: Int? = nil) -> AnyPublisher<[NoticeArticleDto], Error> { + func execute(noticeId: Int? = nil) -> AnyPublisher<[NoticeArticleDto], ErrorResponse> { return noticeListRepository.fetchHotNoticeArticle() .map { noticeArticles in var filteredArticles: [NoticeArticleDto] diff --git a/Koin/Domain/UseCase/NoticeList/FetchHotSearchingKeywordUseCase.swift b/Koin/Domain/UseCase/NoticeList/FetchHotSearchingKeywordUseCase.swift index ff8b56a8e..fa214bd8c 100644 --- a/Koin/Domain/UseCase/NoticeList/FetchHotSearchingKeywordUseCase.swift +++ b/Koin/Domain/UseCase/NoticeList/FetchHotSearchingKeywordUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchHotSearchingKeywordUseCase { - func execute(count: Int) -> AnyPublisher<[String], Error> + func execute(count: Int) -> AnyPublisher<[String], ErrorResponse> } final class DefaultFetchHotSearchingKeywordUseCase: FetchHotSearchingKeywordUseCase { @@ -18,7 +18,7 @@ final class DefaultFetchHotSearchingKeywordUseCase: FetchHotSearchingKeywordUseC self.noticeListRepository = noticeListRepository } - func execute(count: Int) -> AnyPublisher<[String], Error> { + func execute(count: Int) -> AnyPublisher<[String], ErrorResponse> { return noticeListRepository.fetchRecommendedKeyword(count: count).map { keywords in return keywords.keywords.map { keyword in "#\(keyword)" diff --git a/Koin/Domain/UseCase/NoticeList/FetchNoticeArticlesUseCase.swift b/Koin/Domain/UseCase/NoticeList/FetchNoticeArticlesUseCase.swift index 198626ac3..d0aa4da48 100644 --- a/Koin/Domain/UseCase/NoticeList/FetchNoticeArticlesUseCase.swift +++ b/Koin/Domain/UseCase/NoticeList/FetchNoticeArticlesUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchNoticeArticlesUseCase { - func execute(boardId: Int?, keyWord: String?, page: Int, type: LostItemType?) -> AnyPublisher + func execute(boardId: Int?, keyWord: String?, page: Int, type: LostItemType?) -> AnyPublisher } final class DefaultFetchNoticeArticlesUseCase: FetchNoticeArticlesUseCase { @@ -21,8 +21,8 @@ final class DefaultFetchNoticeArticlesUseCase: FetchNoticeArticlesUseCase { self.noticeListRepository = noticeListRepository } - func execute(boardId: Int?, keyWord: String?, page: Int, type: LostItemType? = nil) -> AnyPublisher { - let response: AnyPublisher + func execute(boardId: Int?, keyWord: String?, page: Int, type: LostItemType? = nil) -> AnyPublisher { + let response: AnyPublisher if let keyWord = keyWord { // 키워드로 검색해서 찾을 경우 let searchRequest = SearchNoticeArticleRequest(query: keyWord, boardId: boardId, page: page, limit: maxArticleListNumber) @@ -36,7 +36,7 @@ final class DefaultFetchNoticeArticlesUseCase: FetchNoticeArticlesUseCase { response = noticeListRepository.fetchNoticeArticles(requestModel: fetchRequest) } else { // boardId는 꼭 필요하기 때문에 없다면 빈 배열을 반환 response = Just(NoticeListDto(articles: [], totalCount: 0, currentCount: 0, totalPage: 0, currentPage: 0)) - .setFailureType(to: Error.self) + .setFailureType(to: ErrorResponse.self) .eraseToAnyPublisher() } diff --git a/Koin/Domain/UseCase/NoticeList/FetchNoticeDataUseCase.swift b/Koin/Domain/UseCase/NoticeList/FetchNoticeDataUseCase.swift index ff426dac6..6a9d78dc4 100644 --- a/Koin/Domain/UseCase/NoticeList/FetchNoticeDataUseCase.swift +++ b/Koin/Domain/UseCase/NoticeList/FetchNoticeDataUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchNoticeDataUseCase { - func execute(request: FetchNoticeDataRequest) -> AnyPublisher + func execute(request: FetchNoticeDataRequest) -> AnyPublisher } final class DefaultFetchNoticeDataUseCase: FetchNoticeDataUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchNoticeDataUseCase: FetchNoticeDataUseCase { self.noticeListRepository = noticeListRepository } - func execute(request: FetchNoticeDataRequest) -> AnyPublisher { + func execute(request: FetchNoticeDataRequest) -> AnyPublisher { return noticeListRepository.fetchNoticeData(requestModel: request).map { data in data.toDomainWithChangedDate().toDomain() }.eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/NoticeList/FetchRecommendedKeywordUseCase.swift b/Koin/Domain/UseCase/NoticeList/FetchRecommendedKeywordUseCase.swift index 1081cc356..251a849b0 100644 --- a/Koin/Domain/UseCase/NoticeList/FetchRecommendedKeywordUseCase.swift +++ b/Koin/Domain/UseCase/NoticeList/FetchRecommendedKeywordUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchRecommendedKeywordUseCase { - func execute(filters: [NoticeKeywordDto]) -> AnyPublisher + func execute(filters: [NoticeKeywordDto]) -> AnyPublisher } final class DefaultFetchRecommendedKeywordUseCase: FetchRecommendedKeywordUseCase { @@ -18,7 +18,7 @@ final class DefaultFetchRecommendedKeywordUseCase: FetchRecommendedKeywordUseCas self.noticeListRepository = noticeListRepository } - func execute(filters: [NoticeKeywordDto]) -> AnyPublisher { + func execute(filters: [NoticeKeywordDto]) -> AnyPublisher { return noticeListRepository.fetchRecommendedKeyword(count: nil) .map { recommendedKeywordDto in let filteredKeywords = recommendedKeywordDto.keywords.filter { keyword in diff --git a/Koin/Domain/UseCase/NoticeList/SearchNoticeArticlesUseCase.swift b/Koin/Domain/UseCase/NoticeList/SearchNoticeArticlesUseCase.swift index a3a0c4d56..0e5f6cbeb 100644 --- a/Koin/Domain/UseCase/NoticeList/SearchNoticeArticlesUseCase.swift +++ b/Koin/Domain/UseCase/NoticeList/SearchNoticeArticlesUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol SearchNoticeArticlesUseCase { - func execute(requestModel: SearchNoticeArticleRequest) -> AnyPublisher + func execute(requestModel: SearchNoticeArticleRequest) -> AnyPublisher } final class DefaultSearchNoticeArticlesUseCase: SearchNoticeArticlesUseCase { @@ -19,7 +19,7 @@ final class DefaultSearchNoticeArticlesUseCase: SearchNoticeArticlesUseCase { self.noticeRepository = noticeRepository } - func execute(requestModel: SearchNoticeArticleRequest) -> AnyPublisher { + func execute(requestModel: SearchNoticeArticleRequest) -> AnyPublisher { return noticeRepository.searchNoticeArticle(requestModel: requestModel).map { $0.toDomain() }.eraseToAnyPublisher() } } diff --git a/Koin/Domain/UseCase/Shop/FetchBeneficialShopUseCase.swift b/Koin/Domain/UseCase/Shop/FetchBeneficialShopUseCase.swift index b6c235150..3e5f31b3b 100644 --- a/Koin/Domain/UseCase/Shop/FetchBeneficialShopUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchBeneficialShopUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchBeneficialShopUseCase { - func execute(id: Int) -> AnyPublisher<[Shop], Error> + func execute(id: Int) -> AnyPublisher<[Shop], ErrorResponse> } final class DefaultFetchBeneficialShopUseCase: FetchBeneficialShopUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchBeneficialShopUseCase: FetchBeneficialShopUseCase { self.shopRepository = shopRepository } - func execute(id: Int) -> AnyPublisher<[Shop], Error> { + func execute(id: Int) -> AnyPublisher<[Shop], ErrorResponse> { return shopRepository.fetchBeneficialShops(id: id) .map { $0.toDomain() } .eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/Shop/FetchEventListUseCase.swift b/Koin/Domain/UseCase/Shop/FetchEventListUseCase.swift index bd7cfa63a..32635252f 100644 --- a/Koin/Domain/UseCase/Shop/FetchEventListUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchEventListUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchEventListUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchEventListUseCase: FetchEventListUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchEventListUseCase: FetchEventListUseCase { self.shopRepository = shopRepository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return shopRepository.fetchEventList() } } diff --git a/Koin/Domain/UseCase/Shop/FetchOrderShopDetailFromShopUseCase.swift b/Koin/Domain/UseCase/Shop/FetchOrderShopDetailFromShopUseCase.swift index 00aaa1759..de7a26e8a 100644 --- a/Koin/Domain/UseCase/Shop/FetchOrderShopDetailFromShopUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchOrderShopDetailFromShopUseCase.swift @@ -9,7 +9,7 @@ import Foundation import Combine protocol FetchOrderShopDetailFromShopUseCase { - func execute(shopId: Int) -> AnyPublisher + func execute(shopId: Int) -> AnyPublisher } final class DefaultFetchOrderShopDetailFromShopUseCase: FetchOrderShopDetailFromShopUseCase { @@ -20,7 +20,7 @@ final class DefaultFetchOrderShopDetailFromShopUseCase: FetchOrderShopDetailFrom self.repository = repository } - func execute(shopId: Int) -> AnyPublisher { + func execute(shopId: Int) -> AnyPublisher { repository.fetchShopData(requestModel: FetchShopDataRequest(shopId: shopId)).map { shopDataDto in return OrderShopDetail(from: shopDataDto) }.eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/Shop/FetchOrderShopMenusAndGroupsFromShopUseCase.swift b/Koin/Domain/UseCase/Shop/FetchOrderShopMenusAndGroupsFromShopUseCase.swift index 240c64476..deee025f3 100644 --- a/Koin/Domain/UseCase/Shop/FetchOrderShopMenusAndGroupsFromShopUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchOrderShopMenusAndGroupsFromShopUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchOrderShopMenusAndGroupsFromShopUseCase { - func execute(shopId: Int) -> AnyPublisher<(OrderShopMenusGroups, [OrderShopMenus]), Error> + func execute(shopId: Int) -> AnyPublisher<(OrderShopMenusGroups, [OrderShopMenus]), ErrorResponse> } final class DefaultFetchOrderShopMenusAndGroupsFromShopUseCase: FetchOrderShopMenusAndGroupsFromShopUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchOrderShopMenusAndGroupsFromShopUseCase: FetchOrderShopMe self.shopRepository = shopRepository } - func execute(shopId: Int) -> AnyPublisher<(OrderShopMenusGroups, [OrderShopMenus]), Error> { + func execute(shopId: Int) -> AnyPublisher<(OrderShopMenusGroups, [OrderShopMenus]), ErrorResponse> { return shopRepository.fetchShopMenuList(requestModel: FetchShopDataRequest(shopId: shopId)).map { menuDto in let orderShopMenus: [OrderShopMenus] = (menuDto.menuCategories ?? []).map { OrderShopMenus(from: $0) diff --git a/Koin/Domain/UseCase/Shop/FetchOrderShopSummaryFromShopUseCase.swift b/Koin/Domain/UseCase/Shop/FetchOrderShopSummaryFromShopUseCase.swift index 500cef881..30e9374be 100644 --- a/Koin/Domain/UseCase/Shop/FetchOrderShopSummaryFromShopUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchOrderShopSummaryFromShopUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchOrderShopSummaryFromShopUseCase { - func execute(id: Int) -> AnyPublisher + func execute(id: Int) -> AnyPublisher } final class DefaultFetchOrderShopSummaryFromShopUseCase: FetchOrderShopSummaryFromShopUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchOrderShopSummaryFromShopUseCase: FetchOrderShopSummaryFr self.repository = repository } - func execute(id: Int) -> AnyPublisher { + func execute(id: Int) -> AnyPublisher { return repository.fetchShopSummary(id: id) .map { shopSummaryDto in return OrderShopSummary(from: shopSummaryDto, shopId: id) diff --git a/Koin/Domain/UseCase/Shop/FetchSearchShopUseCase.swift b/Koin/Domain/UseCase/Shop/FetchSearchShopUseCase.swift index 2796672d3..5a0f3c264 100644 --- a/Koin/Domain/UseCase/Shop/FetchSearchShopUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchSearchShopUseCase.swift @@ -9,7 +9,7 @@ import Foundation import Combine protocol FetchSearchShopUseCase { - func execute(keyword: String) -> AnyPublisher + func execute(keyword: String) -> AnyPublisher } final class DefaultFetchSearchShopUseCase: FetchSearchShopUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchSearchShopUseCase: FetchSearchShopUseCase { self.repository = repository } - func execute(keyword: String) -> AnyPublisher { + func execute(keyword: String) -> AnyPublisher { let requestModel = FetchShopSearchRequest(keyword: keyword) return repository.fetchSearchShop(requestModel: requestModel) } diff --git a/Koin/Domain/UseCase/Shop/FetchShopBenefitUseCase.swift b/Koin/Domain/UseCase/Shop/FetchShopBenefitUseCase.swift index 831347124..8b435d2c6 100644 --- a/Koin/Domain/UseCase/Shop/FetchShopBenefitUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchShopBenefitUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchShopBenefitUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchShopBenefitUseCase: FetchShopBenefitUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchShopBenefitUseCase: FetchShopBenefitUseCase { self.shopRepository = shopRepository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return shopRepository.fetchShopBenefits() } } diff --git a/Koin/Domain/UseCase/Shop/FetchShopCategoryListUseCase.swift b/Koin/Domain/UseCase/Shop/FetchShopCategoryListUseCase.swift index 1dd961e6c..b16a87b8f 100644 --- a/Koin/Domain/UseCase/Shop/FetchShopCategoryListUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchShopCategoryListUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchShopCategoryListUseCase { - func execute() -> AnyPublisher + func execute() -> AnyPublisher } final class DefaultFetchShopCategoryListUseCase: FetchShopCategoryListUseCase { @@ -18,7 +18,7 @@ final class DefaultFetchShopCategoryListUseCase: FetchShopCategoryListUseCase { self.shopRepository = shopRepository } - func execute() -> AnyPublisher { + func execute() -> AnyPublisher { return shopRepository.fetchShopCategoryList() .map { shopCategoryDto in return self.removeFirstCategory(in: shopCategoryDto) diff --git a/Koin/Domain/UseCase/Shop/FetchShopDataUseCase.swift b/Koin/Domain/UseCase/Shop/FetchShopDataUseCase.swift index 545db0f89..b5c2d2cc2 100644 --- a/Koin/Domain/UseCase/Shop/FetchShopDataUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchShopDataUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchShopDataUseCase { - func execute(shopId: Int) -> AnyPublisher + func execute(shopId: Int) -> AnyPublisher } final class DefaultFetchShopDataUseCase: FetchShopDataUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchShopDataUseCase: FetchShopDataUseCase { self.shopRepository = shopRepository } - func execute(shopId: Int) -> AnyPublisher { + func execute(shopId: Int) -> AnyPublisher { return shopRepository.fetchShopData(requestModel: FetchShopDataRequest(shopId: shopId)).map { shopDataDto in shopDataDto.toDomain() }.eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/Shop/FetchShopEventListUseCase.swift b/Koin/Domain/UseCase/Shop/FetchShopEventListUseCase.swift index 77903a9cf..9bd9ac11c 100644 --- a/Koin/Domain/UseCase/Shop/FetchShopEventListUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchShopEventListUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchShopEventListUseCase { - func execute(shopId: Int) -> AnyPublisher<[ShopEvent], Error> + func execute(shopId: Int) -> AnyPublisher<[ShopEvent], ErrorResponse> } final class DefaultFetchShopEventListUseCase: FetchShopEventListUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchShopEventListUseCase: FetchShopEventListUseCase { self.shopRepository = shopRepository } - func execute(shopId: Int) -> AnyPublisher<[ShopEvent], Error> { + func execute(shopId: Int) -> AnyPublisher<[ShopEvent], ErrorResponse> { return shopRepository.fetchShopEventList(requestModel: FetchShopDataRequest(shopId: shopId)).map { eventsDto in eventsDto.events ?? [] }.map { events in diff --git a/Koin/Domain/UseCase/Shop/FetchShopListUseCase.swift b/Koin/Domain/UseCase/Shop/FetchShopListUseCase.swift index 7a13a6238..22b061c64 100644 --- a/Koin/Domain/UseCase/Shop/FetchShopListUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchShopListUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchShopListUseCase { - func execute(requestModel: FetchShopListRequest) -> AnyPublisher<[Shop], Error> + func execute(requestModel: FetchShopListRequest) -> AnyPublisher<[Shop], ErrorResponse> } final class DefaultFetchShopListUseCase: FetchShopListUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchShopListUseCase: FetchShopListUseCase { self.shopRepository = shopRepository } - func execute(requestModel: FetchShopListRequest) -> AnyPublisher<[Shop], Error> { + func execute(requestModel: FetchShopListRequest) -> AnyPublisher<[Shop], ErrorResponse> { return shopRepository.fetchShopList(requestModel: requestModel) .map { dto in return dto.toDomain() diff --git a/Koin/Domain/UseCase/Shop/FetchShopMenuListUseCase.swift b/Koin/Domain/UseCase/Shop/FetchShopMenuListUseCase.swift index 322fca73a..480010733 100644 --- a/Koin/Domain/UseCase/Shop/FetchShopMenuListUseCase.swift +++ b/Koin/Domain/UseCase/Shop/FetchShopMenuListUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchShopMenuListUseCase { - func execute(shopId: Int) -> AnyPublisher<[MenuCategory], Error> + func execute(shopId: Int) -> AnyPublisher<[MenuCategory], ErrorResponse> } final class DefaultFetchShopMenuListUseCase: FetchShopMenuListUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchShopMenuListUseCase: FetchShopMenuListUseCase { self.shopRepository = shopRepository } - func execute(shopId: Int) -> AnyPublisher<[MenuCategory], Error> { + func execute(shopId: Int) -> AnyPublisher<[MenuCategory], ErrorResponse> { return shopRepository.fetchShopMenuList(requestModel: FetchShopDataRequest(shopId: shopId)).map { menuDto in menuDto.menuCategories ?? [] }.eraseToAnyPublisher() diff --git a/Koin/Domain/UseCase/Shop/SearchShopUseCase.swift b/Koin/Domain/UseCase/Shop/SearchShopUseCase.swift index 2220082ae..5d4ad2943 100644 --- a/Koin/Domain/UseCase/Shop/SearchShopUseCase.swift +++ b/Koin/Domain/UseCase/Shop/SearchShopUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol SearchShopUseCase { - func execute(text: String) -> AnyPublisher + func execute(text: String) -> AnyPublisher } final class DefaultSearchShopUseCase: SearchShopUseCase { @@ -18,7 +18,7 @@ final class DefaultSearchShopUseCase: SearchShopUseCase { self.shopRepository = shopRepository } - func execute(text: String) -> AnyPublisher { + func execute(text: String) -> AnyPublisher { return shopRepository.searchRelatedQuery(text: text) } diff --git a/Koin/Domain/UseCase/Timetable/FetchDeptListUseCase.swift b/Koin/Domain/UseCase/Timetable/FetchDeptListUseCase.swift index a4aacf3f5..4ca288069 100644 --- a/Koin/Domain/UseCase/Timetable/FetchDeptListUseCase.swift +++ b/Koin/Domain/UseCase/Timetable/FetchDeptListUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchDeptListUseCase { - func execute() -> AnyPublisher<[String], Error> + func execute() -> AnyPublisher<[String], ErrorResponse> } final class DefaultFetchDeptListUseCase: FetchDeptListUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchDeptListUseCase: FetchDeptListUseCase { self.timetableRepository = timetableRepository } - func execute() -> AnyPublisher<[String], Error> { + func execute() -> AnyPublisher<[String], ErrorResponse> { return timetableRepository.fetchDeptList().map { $0.map { $0.name } }.eraseToAnyPublisher() } diff --git a/Koin/Domain/UseCase/Timetable/FetchLectureListUseCase.swift b/Koin/Domain/UseCase/Timetable/FetchLectureListUseCase.swift index dd41e04e1..10023abd2 100644 --- a/Koin/Domain/UseCase/Timetable/FetchLectureListUseCase.swift +++ b/Koin/Domain/UseCase/Timetable/FetchLectureListUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchLectureListUseCase { - func execute(semester: String) -> AnyPublisher<[SemesterLecture], Error> + func execute(semester: String) -> AnyPublisher<[SemesterLecture], ErrorResponse> } final class DefaultFetchLectureListUseCase: FetchLectureListUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchLectureListUseCase: FetchLectureListUseCase { self.timetableRepository = timetableRepository } - func execute(semester: String) -> AnyPublisher<[SemesterLecture], Error> { + func execute(semester: String) -> AnyPublisher<[SemesterLecture], ErrorResponse> { return timetableRepository.fetchLectureList(semester: semester) } diff --git a/Koin/Domain/UseCase/Timetable/FetchSemesterUseCase.swift b/Koin/Domain/UseCase/Timetable/FetchSemesterUseCase.swift index 5c5c446fd..3e7fd86ff 100644 --- a/Koin/Domain/UseCase/Timetable/FetchSemesterUseCase.swift +++ b/Koin/Domain/UseCase/Timetable/FetchSemesterUseCase.swift @@ -8,7 +8,7 @@ import Combine protocol FetchSemesterUseCase { - func execute() -> AnyPublisher<[SemesterDto], Error> + func execute() -> AnyPublisher<[SemesterDto], ErrorResponse> } final class DefaultFetchSemesterUseCase: FetchSemesterUseCase { @@ -19,7 +19,7 @@ final class DefaultFetchSemesterUseCase: FetchSemesterUseCase { self.timetableRepository = timetableRepository } - func execute() -> AnyPublisher<[SemesterDto], Error> { + func execute() -> AnyPublisher<[SemesterDto], ErrorResponse> { return timetableRepository.fetchSemester() } diff --git a/Koin/Domain/UseCase/User/RegisterUseCase.swift b/Koin/Domain/UseCase/User/RegisterUseCase.swift index 171482098..53b7f3b34 100644 --- a/Koin/Domain/UseCase/User/RegisterUseCase.swift +++ b/Koin/Domain/UseCase/User/RegisterUseCase.swift @@ -28,7 +28,7 @@ final class DefaultRegisterUseCase: RegisterUseCase { mutableRequestModel.sha256() return userRepository.register(requestModel: mutableRequestModel) case .failure(let error): - return Fail(error: ErrorResponse(code: "", message: error.localizedDescription)) + return Fail(error: ErrorResponse(error)) .eraseToAnyPublisher() } } diff --git a/Koin/Presentation/Bus/BusSearch/BusSearchViewModel.swift b/Koin/Presentation/Bus/BusSearch/BusSearchViewModel.swift index 6831b9fec..6dd962cbc 100644 --- a/Koin/Presentation/Bus/BusSearch/BusSearchViewModel.swift +++ b/Koin/Presentation/Bus/BusSearch/BusSearchViewModel.swift @@ -64,13 +64,12 @@ extension BusSearchViewModel { } private func getEmergencyNotice() { - fetchEmergencyNoticeUseCase.execute().sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchEmergencyNoticeUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] notice in + self?.outputSubject.send(.updateEmergencyNotice(notice: notice)) } - }, receiveValue: { [weak self] notice in - self?.outputSubject.send(.updateEmergencyNotice(notice: notice)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Bus/BusSearchResult/BusSearchResultViewModel.swift b/Koin/Presentation/Bus/BusSearchResult/BusSearchResultViewModel.swift index c817fa73b..271bc17b2 100644 --- a/Koin/Presentation/Bus/BusSearchResult/BusSearchResultViewModel.swift +++ b/Koin/Presentation/Bus/BusSearchResult/BusSearchResultViewModel.swift @@ -74,24 +74,22 @@ extension BusSearchResultViewModel { self.departBusType = busType busTypeValue = busType } - fetchSearchedResultUseCase.execute(date: departDateValue, busType: busTypeValue, departure: busPlaces.0, arrival: busPlaces.1).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchSearchedResultUseCase.execute(date: departDateValue, busType: busTypeValue, departure: busPlaces.0, arrival: busPlaces.1).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] searchedResult in + let output = departDate != nil ? (departDate, searchedResult) : (nil, searchedResult) + self?.outputSubject.send(.udpatesSearchedResult(output.0, output.1)) } - }, receiveValue: { [weak self] searchedResult in - let output = departDate != nil ? (departDate, searchedResult) : (nil, searchedResult) - self?.outputSubject.send(.udpatesSearchedResult(output.0, output.1)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func getSemesterInfo() { - fetchSemesterInfoUseCase.execute(busRouteType: .overall).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchSemesterInfoUseCase.execute(busRouteType: .overall).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.updateSemesterInfo(response.semesterInfo)) } - }, receiveValue: { [weak self] response in - self?.outputSubject.send(.updateSemesterInfo(response.semesterInfo)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Bus/BusTimetable/BusTimetableViewModel.swift b/Koin/Presentation/Bus/BusTimetable/BusTimetableViewModel.swift index 2d2695bf1..21dec5073 100644 --- a/Koin/Presentation/Bus/BusTimetable/BusTimetableViewModel.swift +++ b/Koin/Presentation/Bus/BusTimetable/BusTimetableViewModel.swift @@ -80,44 +80,39 @@ final class BusTimetableViewModel: ViewModelProtocol { private func getBusTimetable(busType: BusType, firstFilterIdx: Int, secondFilterIdx: Int?) { switch busType { case .shuttleBus: - fetchShuttleRoutesUseCase.execute(busRouteType: ShuttleRouteType.allCases[firstFilterIdx]).sink(receiveCompletion: { - completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchShuttleRoutesUseCase.execute(busRouteType: ShuttleRouteType.allCases[firstFilterIdx]).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] busRoutes in + self?.outputSubject.send(.updateShuttleBusRoutes(busRoutes: busRoutes)) } - }, receiveValue: { [weak self] busRoutes in - self?.outputSubject.send(.updateShuttleBusRoutes(busRoutes: busRoutes)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) case .expressBus: - fetchExpressTimetableUseCase.execute(filterIdx: firstFilterIdx).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchExpressTimetableUseCase.execute(filterIdx: firstFilterIdx).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] timetable, busDirection in + self?.outputSubject.send(.updateBusTimetable(busType: busType, busTimetableInfo: timetable)) + let busStop = busDirection == .from ? "천안 터미널 승차" : "코리아텍 승차" + self?.outputSubject.send(.updateStopLabel(busStop: busStop)) } - }, receiveValue: { [weak self] timetable, busDirection in - self?.outputSubject.send(.updateBusTimetable(busType: busType, busTimetableInfo: timetable)) - let busStop = busDirection == .from ? "천안 터미널 승차" : "코리아텍 승차" - self?.outputSubject.send(.updateStopLabel(busStop: busStop)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) default: - fetchCityTimetableUseCase.execute(firstFilterIdx: firstFilterIdx, secondFilterIdx: secondFilterIdx ?? 0).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchCityTimetableUseCase.execute(firstFilterIdx: firstFilterIdx, secondFilterIdx: secondFilterIdx ?? 0).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] timetable, busDirection in + self?.outputSubject.send(.updateBusTimetable(busType: busType, busTimetableInfo: timetable)) + self?.outputSubject.send(.updateStopLabel(busStop: "\(busDirection) 승차")) } - }, receiveValue: { [weak self] timetable, busDirection in - self?.outputSubject.send(.updateBusTimetable(busType: busType, busTimetableInfo: timetable)) - self?.outputSubject.send(.updateStopLabel(busStop: "\(busDirection) 승차")) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } } private func getEmergencyNotice() { - fetchEmergencyNoticeUseCase.execute().sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchEmergencyNoticeUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] notice in + self?.outputSubject.send(.updateEmergencyNotice(notice: notice)) } - }, receiveValue: { [weak self] notice in - self?.outputSubject.send(.updateEmergencyNotice(notice: notice)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Bus/BusTimetableData/BusTimetableDataViewModel.swift b/Koin/Presentation/Bus/BusTimetableData/BusTimetableDataViewModel.swift index 17194269c..b4d7d349a 100644 --- a/Koin/Presentation/Bus/BusTimetableData/BusTimetableDataViewModel.swift +++ b/Koin/Presentation/Bus/BusTimetableData/BusTimetableDataViewModel.swift @@ -49,17 +49,13 @@ final class BusTimetableDataViewModel: ViewModelProtocol { } private func getShuttleBusTimetable(shuttleTimetableType: ShuttleTimetableType) { - fetchShuttleTimetableUseCase.execute(id: shuttleRouteId) - .sink { [weak self] completion in - guard self != nil else { return } - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] timetable in + fetchShuttleTimetableUseCase.execute(id: shuttleRouteId).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] timetable in guard let self else { return } self.outputSubject.send(.updateBusRoute(timetable, shuttleTimetableType)) } - .store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Chat/Chat/ChatViewModel.swift b/Koin/Presentation/Chat/Chat/ChatViewModel.swift index 4e4f9b201..9cb999f81 100644 --- a/Koin/Presentation/Chat/Chat/ChatViewModel.swift +++ b/Koin/Presentation/Chat/Chat/ChatViewModel.swift @@ -106,7 +106,6 @@ extension ChatViewModel { guard let self else { return Empty().eraseToAnyPublisher() } return fetchChatDetailUseCase.execute(userId: UserDataManager.shared.id, articleId: articleId, chatRoomId: chatRoomId) .catch { error -> AnyPublisher<[ChatMessage], Never> in - Log.make().error("\(error)") return Empty().eraseToAnyPublisher() } .eraseToAnyPublisher() @@ -120,10 +119,7 @@ extension ChatViewModel { private func sendMessage(message: String, isImage: Bool) { postChatDetailUseCase.execute(articleId: articleId, chatRoomId: chatRoomId, message: message, isImage: isImage).sink( receiveCompletion: { [weak self] completion in - switch completion { - case .failure(let error): - Log.make().error("\(error)") - case .finished: + if case .finished = completion { self?.fetchChatDetail() } }, diff --git a/Koin/Presentation/Chat/ChatList/ChatListTableViewModel.swift b/Koin/Presentation/Chat/ChatList/ChatListTableViewModel.swift index 912f86195..02e6acdae 100644 --- a/Koin/Presentation/Chat/ChatList/ChatListTableViewModel.swift +++ b/Koin/Presentation/Chat/ChatList/ChatListTableViewModel.swift @@ -71,7 +71,6 @@ extension ChatListTableViewModel { guard let self else { return Empty().eraseToAnyPublisher() } return fetchChatRoomUseCase.execute() .catch { error -> AnyPublisher<[ChatRoomItem], Never> in - Log.make().error("\(error)") return Empty().eraseToAnyPublisher() }.eraseToAnyPublisher() } diff --git a/Koin/Presentation/Club/ClubWebViewController.swift b/Koin/Presentation/Club/ClubWebViewController.swift index 7e445e6bd..60d5c429a 100644 --- a/Koin/Presentation/Club/ClubWebViewController.swift +++ b/Koin/Presentation/Club/ClubWebViewController.swift @@ -71,15 +71,14 @@ final class ClubWebViewController: UIViewController { } private func checkLogin() { - checkLoginUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + checkLoginUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + guard let self = self else { return } + setupWebView() + loadClubPage() } - } receiveValue: { [weak self] response in - guard let self = self else { return } - setupWebView() - loadClubPage() - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func loadClubPage() { diff --git a/Koin/Presentation/Dining/Dining/DiningViewModel.swift b/Koin/Presentation/Dining/Dining/DiningViewModel.swift index 08f5b8ed4..cdbb4e454 100644 --- a/Koin/Presentation/Dining/Dining/DiningViewModel.swift +++ b/Koin/Presentation/Dining/Dining/DiningViewModel.swift @@ -96,15 +96,14 @@ final class DiningViewModel: ViewModelProtocol { extension DiningViewModel { private func fetchNotiList() { - fetchNotiListUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchNotiListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + guard let self = self else { return } + let tuple = self.processSubscribes(response: response) + self.outputSubject.send(.showBottomSheet(tuple)) } - } receiveValue: { [weak self] response in - guard let self = self else { return } - let tuple = self.processSubscribes(response: response) - self.outputSubject.send(.showBottomSheet(tuple)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func processSubscribes(response: NotiAgreementDto) -> (Bool, Bool) { @@ -126,27 +125,25 @@ extension DiningViewModel { } private func changeNoti(isOn: Bool, type: SubscribeType) { - changeNotiUseCase.execute(method: isOn ? .post : .delete , type: type).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] response in - if type == .diningSoldOut && isOn { - DetailSubscribeType.allCases.forEach { - self?.changeNotiDetail(isOn: isOn, detailType: $0) + changeNotiUseCase.execute(method: isOn ? .post : .delete , type: type).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + if type == .diningSoldOut && isOn { + DetailSubscribeType.allCases.forEach { + self?.changeNotiDetail(isOn: isOn, detailType: $0) + } } } - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func changeNotiDetail(isOn: Bool, detailType: DetailSubscribeType) { - changeNotiDetailUseCase.execute(method: isOn ? .post : .delete , detailType: detailType).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + changeNotiDetailUseCase.execute(method: isOn ? .post : .delete , detailType: detailType).sink( + receiveCompletion: { _ in }, + receiveValue: { response in + print(response) } - } receiveValue: { response in - print(response) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func shareMenuList(shareModel: ShareDiningMenu) { @@ -163,14 +160,13 @@ extension DiningViewModel { } sharedDiningItem = nil - fetchDiningListUseCase.execute(diningInfo: fetchDate).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchDiningListUseCase.execute(diningInfo: fetchDate).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] list in + guard let self = self else { return } + self.outputSubject.send(.updateDiningList(list, currentDate.diningType)) } - } receiveValue: { [weak self] list in - guard let self = self else { return } - self.outputSubject.send(.updateDiningList(list, currentDate.diningType)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func updateDisplayDateTime(date: Date?, type: DiningType?) { diff --git a/Koin/Presentation/Dining/DiningNotice/DiningNoticeViewModel.swift b/Koin/Presentation/Dining/DiningNotice/DiningNoticeViewModel.swift index d0fa94330..c925ee6a7 100644 --- a/Koin/Presentation/Dining/DiningNotice/DiningNoticeViewModel.swift +++ b/Koin/Presentation/Dining/DiningNotice/DiningNoticeViewModel.swift @@ -38,15 +38,12 @@ final class DiningNoticeViewModel: ViewModelProtocol { extension DiningNoticeViewModel { private func fetchCoopShopList() { - fetchCoopShopListUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchCoopShopListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showCoopShopData(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showCoopShopData(response)) - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } - } diff --git a/Koin/Presentation/Home/ForceUpdate/ForceUpdateViewModel.swift b/Koin/Presentation/Home/ForceUpdate/ForceUpdateViewModel.swift index 02245d5d0..e4a9c2553 100644 --- a/Koin/Presentation/Home/ForceUpdate/ForceUpdateViewModel.swift +++ b/Koin/Presentation/Home/ForceUpdate/ForceUpdateViewModel.swift @@ -43,15 +43,14 @@ final class ForceUpdateViewModel: ViewModelProtocol { extension ForceUpdateViewModel { private func checkVersion() { - checkVersionUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + checkVersionUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.isLowVersion(response.0)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.isLowVersion(response.0)) - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } + private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { logAnalyticsEventUseCase.execute(label: label, category: category, value: value) diff --git a/Koin/Presentation/Home/Home/HomeViewModel.swift b/Koin/Presentation/Home/Home/HomeViewModel.swift index 58c6f718f..345a4137b 100644 --- a/Koin/Presentation/Home/Home/HomeViewModel.swift +++ b/Koin/Presentation/Home/Home/HomeViewModel.swift @@ -122,79 +122,73 @@ final class HomeViewModel: ViewModelProtocol { extension HomeViewModel { private func fetchBanner() { - fetchBannerUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchBannerUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.updateBanner(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.updateBanner(response)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } + private func fetchHotClub() { - fetchHotClubsUseCase.execute().sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchHotClubsUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] hotClub in + self?.outputSubject.send(.setHotClub(hotClub)) } - }, receiveValue: { [weak self] hotClub in - self?.outputSubject.send(.setHotClub(hotClub)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func fetchClubCategories() { - fetchClubCategoriesUseCase.execute().sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchClubCategoriesUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] categories in + self?.outputSubject.send(.setClubCategories(categories)) } - }, receiveValue: { [weak self] categories in - self?.outputSubject.send(.setClubCategories(categories)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func fetchUserData() { - fetchUserDataUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] response in - UserDataManager.shared.setUserData(userData: response) - if !UserDefaults.standard.bool(forKey: "forceModal") { - if response.userType == "STUDENT" { - if response.name == nil || - response.phoneNumber == nil || - response.gender == nil || - response.major == nil || - response.studentNumber == nil { - self?.outputSubject.send(.showForceModal) - UserDefaults.standard.set(true, forKey: "forceModal") + fetchUserDataUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + UserDataManager.shared.setUserData(userData: response) + if !UserDefaults.standard.bool(forKey: "forceModal") { + if response.userType == "STUDENT" { + if response.name == nil || + response.phoneNumber == nil || + response.gender == nil || + response.major == nil || + response.studentNumber == nil { + self?.outputSubject.send(.showForceModal) + UserDefaults.standard.set(true, forKey: "forceModal") + } } } } - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func checkVersion() { - checkVersionUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] response in - if response.0 { - self?.outputSubject.send(.showForceUpdate(response.1)) + checkVersionUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + if response.0 { + self?.outputSubject.send(.showForceUpdate(response.1)) + } } - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } private func getDiningInformation(diningPlace: DiningPlace = .cornerA) { let dateInfo = dateProvider.execute(date: Date()) - fetchDiningListUseCase.execute(diningInfo: dateInfo).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchDiningListUseCase.execute(diningInfo: dateInfo).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + let result = response.filter { $0.place == diningPlace }.first + self?.outputSubject.send(.updateDining(result, dateInfo.diningType, dateInfo.date.formatDateToYYMMDD() == Date().formatDateToYYMMDD())) } - } receiveValue: { [weak self] response in - let result = response.filter { $0.place == diningPlace }.first - self?.outputSubject.send(.updateDining(result, dateInfo.diningType, dateInfo.date.formatDateToYYMMDD() == Date().formatDateToYYMMDD())) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func getLostItemStat() { @@ -207,14 +201,13 @@ extension HomeViewModel { } private func getShopCategory() { - fetchShopCategoryListUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchShopCategoryListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.shopCategories = response.shopCategories + self?.outputSubject.send(.putImage(response)) } - } receiveValue: { [weak self] response in - self?.shopCategories = response.shopCategories - self?.outputSubject.send(.putImage(response)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } func getCategoryName(for id: Int) -> String? { @@ -253,18 +246,17 @@ extension HomeViewModel { if let date = date { phrase = fetchKeywordNoticePhraseUseCase.execute(date: date) } - fetchHotNoticeArticlesUseCase.execute(noticeId: nil).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] articles in - if date == nil { - self?.outputSubject.send(.updateNoticeBanners(articles, nil)) - } - else { - self?.outputSubject.send(.updateNoticeBanners(articles, phrase)) + fetchHotNoticeArticlesUseCase.execute(noticeId: nil).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] articles in + if date == nil { + self?.outputSubject.send(.updateNoticeBanners(articles, nil)) + } + else { + self?.outputSubject.send(.updateNoticeBanners(articles, phrase)) + } } - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsSessionEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any, sessionId: String) { diff --git a/Koin/Presentation/Home/Home/SubViews/ErrorViewController.swift b/Koin/Presentation/Home/Home/SubViews/ErrorViewController.swift new file mode 100644 index 000000000..286ee49b5 --- /dev/null +++ b/Koin/Presentation/Home/Home/SubViews/ErrorViewController.swift @@ -0,0 +1,138 @@ +// +// ErrorViewController.swift +// koin +// +// Created by 홍기정 on 2/13/26. +// + +import UIKit + +final class ErrorViewController: UIViewController { + + // MARK: - Properties + private let completion: ()->Void + + // MARK: - UI Components + private let wrapperViewLayoutGuide = UILayoutGuide() + + private let wrapperView = UIView() + + private let warningImageView = UIImageView().then { + let configuration = UIImage.SymbolConfiguration.init(pointSize: 60, weight: .light) + $0.image = UIImage(systemName: "exclamationmark.triangle", withConfiguration: configuration) + $0.tintColor = .appColor(.neutral500) + } + + private let titleLabel = UILabel().then { + $0.text = "개발 중인 페이지입니다" + $0.font = .appFont(.pretendardSemiBold, size: 20) + $0.textColor = .appColor(.primary500) + } + + private let subTitleLabel = UILabel().then { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineHeightMultiple = 1.40 + paragraphStyle.alignment = .center + + $0.attributedText = NSAttributedString( + string: "죄송합니다. 현재 개발 중인 페이지입니다.\n최대한 빠르게 오픈하도록 하겠습니다.", + attributes: [ + .font : UIFont.appFont(.pretendardMedium, size: 14), + .foregroundColor : UIColor.appColor(.neutral500), + .paragraphStyle : paragraphStyle + ]) + $0.numberOfLines = 2 + } + + private let navigateButton = UIButton().then { + $0.setAttributedTitle(NSAttributedString( + string: "메인 화면 바로가기", attributes: [ + .foregroundColor : UIColor.white, + .font : UIFont.appFont(.pretendardSemiBold, size: 15) + ]), for: .normal) + $0.backgroundColor = .appColor(.primary500) + $0.layer.cornerRadius = 8 + $0.layer.masksToBounds = true + } + + // MARK: - Initializer + init(completion: @escaping () -> Void) { + self.completion = completion + super.init(nibName: nil, bundle: nil) + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Life Cycle + override func viewDidLoad() { + super.viewDidLoad() + configureView() + navigateButton.addTarget(self, action: #selector(navigateButtonTapped), for: .touchUpInside) + } +} + +extension ErrorViewController { + + @objc private func navigateButtonTapped() { + completion() + } +} + +extension ErrorViewController { + + private func setUpLayouts() { + [warningImageView, titleLabel, subTitleLabel].forEach { + wrapperView.addSubview($0) + } + + [wrapperView, navigateButton].forEach { + view.addSubview($0) + } + + [wrapperViewLayoutGuide].forEach { + view.addLayoutGuide($0) + } + } + private func setUpConstraints() { + wrapperViewLayoutGuide.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide) + $0.bottom.equalTo(navigateButton.snp.top) + $0.leading.trailing.equalToSuperview() + } + + wrapperView.snp.makeConstraints { + $0.center.equalTo(wrapperViewLayoutGuide) + } + + warningImageView.snp.makeConstraints { + $0.top.equalTo(wrapperView) + $0.centerX.equalTo(wrapperView) + } + + titleLabel.snp.makeConstraints { + $0.top.equalTo(warningImageView.snp.bottom).offset(20) + $0.centerX.equalTo(wrapperView) + } + + subTitleLabel.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(10) + $0.bottom.equalTo(wrapperView) + $0.centerX.equalTo(wrapperView) + } + + navigateButton.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(24) + $0.height.equalTo(46) + $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-16) + } + } + + private func configureView() { + setUpLayouts() + setUpConstraints() + view.backgroundColor = .white + } +} + + diff --git a/Koin/Presentation/Home/ServiceSelect/ServiceSelectViewModel.swift b/Koin/Presentation/Home/ServiceSelect/ServiceSelectViewModel.swift index c2c36a3d5..7176bec33 100644 --- a/Koin/Presentation/Home/ServiceSelect/ServiceSelectViewModel.swift +++ b/Koin/Presentation/Home/ServiceSelect/ServiceSelectViewModel.swift @@ -49,17 +49,18 @@ final class ServiceSelectViewModel: ViewModelProtocol { extension ServiceSelectViewModel { private func fetchUserData() { - fetchUserDataUseCase.execute().sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.isLogined = false - self?.outputSubject.send(.disappearProfile) + fetchUserDataUseCase.execute().sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.isLogined = false + self?.outputSubject.send(.disappearProfile) + } + }, + receiveValue: { [weak self] response in + self?.isLogined = true + self?.outputSubject.send(.updateProfile(response)) } - } receiveValue: { [weak self] response in - self?.isLogined = true - self?.outputSubject.send(.updateProfile(response)) - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } private func logOut() { diff --git a/Koin/Presentation/Land/Land/LandViewModel.swift b/Koin/Presentation/Land/Land/LandViewModel.swift index 8133fefad..8ae1497f4 100644 --- a/Koin/Presentation/Land/Land/LandViewModel.swift +++ b/Koin/Presentation/Land/Land/LandViewModel.swift @@ -41,12 +41,11 @@ final class LandViewModel: ViewModelProtocol { extension LandViewModel { private func getLandList() { - fetchLandListUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchLandListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showLandList(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showLandList(response)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } } diff --git a/Koin/Presentation/Land/LandDetail/LandDetailViewModel.swift b/Koin/Presentation/Land/LandDetail/LandDetailViewModel.swift index 783e22aed..aad822e79 100644 --- a/Koin/Presentation/Land/LandDetail/LandDetailViewModel.swift +++ b/Koin/Presentation/Land/LandDetail/LandDetailViewModel.swift @@ -41,12 +41,11 @@ final class LandDetailViewModel: ViewModelProtocol { extension LandDetailViewModel { private func getLandDetail() { - fetchLandDetailUseCase.execute(landId: landId).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchLandDetailUseCase.execute(landId: landId).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showLandDetail(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showLandDetail(response)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } } diff --git a/Koin/Presentation/Login/FindId/FindIdViewModel.swift b/Koin/Presentation/Login/FindId/FindIdViewModel.swift index a2f2558bc..7bb2c1417 100644 --- a/Koin/Presentation/Login/FindId/FindIdViewModel.swift +++ b/Koin/Presentation/Login/FindId/FindIdViewModel.swift @@ -37,14 +37,16 @@ extension FindIdViewModel { } func checkVerificationCode(phoneNumber: String, code: String) { - checkVerificationCodeUseCase.execute(phoneNumber: phoneNumber, verificationCode: code).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.checkMessagePublisher.send((error.message, false)) + checkVerificationCodeUseCase.execute(phoneNumber: phoneNumber, verificationCode: code).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.checkMessagePublisher.send((error.message, false)) + } + }, + receiveValue: { [weak self] response in + self?.findIdSms(phoneNumber: phoneNumber) } - } receiveValue: { [weak self] response in - self?.findIdSms(phoneNumber: phoneNumber) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } func findIdSms(phoneNumber: String) { @@ -56,7 +58,7 @@ extension FindIdViewModel { self?.checkMessagePublisher.send(("인증번호가 일치합니다.", true)) }.store(in: &subscriptions) } - /// + func sendVerificationEmail(email: String) { sendVerificationEmailUseCase.execute(requestModel: .init(email: email)).sink { [weak self] completion in if case let .failure(error) = completion { @@ -66,24 +68,31 @@ extension FindIdViewModel { self?.sendMessagePublisher.send(("인증번호가 발송되었습니다.", true)) }.store(in: &subscriptions) } + func checkVerificationEmail(email: String, code: String) { - checkVerificationEmailUseCase.execute(requestModel: .init(email: email, verificationCode: code)).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.checkMessagePublisher.send((error.message, false)) + checkVerificationEmailUseCase.execute(requestModel: .init(email: email, verificationCode: code)).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.checkMessagePublisher.send((error.message, false)) + } + }, + receiveValue: { [weak self] response in + self?.findIdEmail(email: email) } - } receiveValue: { [weak self] response in - self?.findIdEmail(email: email) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } + func findIdEmail(email: String) { - findIdEmailUseCase.execute(requestModel: .init(email: email)).sink { [weak self] completion in - if case let .failure(error) = completion { - self?.checkMessagePublisher.send((error.message, false)) + findIdEmailUseCase.execute(requestModel: .init(email: email)).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.checkMessagePublisher.send((error.message, false)) + } + }, + receiveValue: { [weak self] response in + self?.loginId = response.loginID + self?.checkMessagePublisher.send(("인증번호가 일치합니다.", true)) } - } receiveValue: { [weak self] response in - self?.loginId = response.loginID - self?.checkMessagePublisher.send(("인증번호가 일치합니다.", true)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } } diff --git a/Koin/Presentation/Login/FindPassword/FindPasswordViewModel.swift b/Koin/Presentation/Login/FindPassword/FindPasswordViewModel.swift index 3e140aebc..2b9f789d3 100644 --- a/Koin/Presentation/Login/FindPassword/FindPasswordViewModel.swift +++ b/Koin/Presentation/Login/FindPassword/FindPasswordViewModel.swift @@ -85,14 +85,15 @@ extension FindPasswordViewModel { } func checkVerificationCode() { - checkVerificationCodeUseCase.execute(phoneNumber: inputData, verificationCode: certNumber).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.checkMessagePublisher.send((error.message, false)) + checkVerificationCodeUseCase.execute(phoneNumber: inputData, verificationCode: certNumber).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.checkMessagePublisher.send((error.message, false)) + } + }, receiveValue: { [weak self] response in + self?.checkMessagePublisher.send(("인증번호가 일치합니다.", true)) } - } receiveValue: { [weak self] response in - self?.checkMessagePublisher.send(("인증번호가 일치합니다.", true)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } func findPasswordSms() { @@ -104,7 +105,7 @@ extension FindPasswordViewModel { self?.changeSuccessPublisher.send() }.store(in: &subscriptions) } - /// + func sendVerificationEmail() { sendVerificationEmailUseCase.execute(requestModel: .init(email: inputData)).sink { [weak self] completion in if case let .failure(error) = completion { @@ -114,16 +115,19 @@ extension FindPasswordViewModel { self?.sendMessagePublisher.send(("인증번호가 발송되었습니다.", true)) }.store(in: &subscriptions) } + func checkVerificationEmail() { - checkVerificationEmailUseCase.execute(requestModel: .init(email: inputData, verificationCode: certNumber)).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.checkMessagePublisher.send((error.message, false)) + checkVerificationEmailUseCase.execute(requestModel: .init(email: inputData, verificationCode: certNumber)).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.checkMessagePublisher.send((error.message, false)) + } + }, receiveValue: { [weak self] response in + self?.checkMessagePublisher.send(("인증번호가 일치합니다.", true)) } - } receiveValue: { [weak self] response in - self?.checkMessagePublisher.send(("인증번호가 일치합니다.", true)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } + func findPasswordEmail() { findPasswordEmailUseCase.execute(requestModel: .init(loginId: id, email: inputData, newPassword: password)).sink { [weak self] completion in if case let .failure(error) = completion { diff --git a/Koin/Presentation/Login/Register/RegisterFormViewModel.swift b/Koin/Presentation/Login/Register/RegisterFormViewModel.swift index 70b5de27e..3b8de84ea 100644 --- a/Koin/Presentation/Login/Register/RegisterFormViewModel.swift +++ b/Koin/Presentation/Login/Register/RegisterFormViewModel.swift @@ -158,13 +158,12 @@ extension RegisterFormViewModel { } private func fetchDeptList() { - fetchDeptListUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchDeptListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showDeptDropDownList(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showDeptDropDownList(response)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func checkDuplicatedNickname(nickname: String) { diff --git a/Koin/Presentation/LostItem/LostItemData/LostItemDataViewModel.swift b/Koin/Presentation/LostItem/LostItemData/LostItemDataViewModel.swift index 648fc5d67..cbcc7cc29 100644 --- a/Koin/Presentation/LostItem/LostItemData/LostItemDataViewModel.swift +++ b/Koin/Presentation/LostItem/LostItemData/LostItemDataViewModel.swift @@ -102,11 +102,7 @@ extension LostItemDataViewModel { private func loadData() { fetchLostItemDataUseCase.execute(id: id).sink( - receiveCompletion: { completion in - if case .failure(let failure) = completion { - print(failure) - } - }, + receiveCompletion: { _ in }, receiveValue: { [weak self] lostItemData in self?.lostItemData = lostItemData self?.type = lostItemData.type diff --git a/Koin/Presentation/LostItem/LostItemList/LostItemListViewModel.swift b/Koin/Presentation/LostItem/LostItemList/LostItemListViewModel.swift index 53be1f1c7..ef0e70c93 100644 --- a/Koin/Presentation/LostItem/LostItemList/LostItemListViewModel.swift +++ b/Koin/Presentation/LostItem/LostItemList/LostItemListViewModel.swift @@ -88,7 +88,7 @@ extension LostItemListViewModel { fetchLostItemListUseCase.execute(requestModel: filterState).sink( receiveCompletion: { [weak self] completion in - if case .failure(_) = completion { + if case let .failure(error) = completion { self?.filterState.page -= 1 } }, @@ -103,7 +103,7 @@ extension LostItemListViewModel { private func checkLogin() { checkLoginUseCase.execute().sink( - receiveCompletion: { _ in}, + receiveCompletion: { _ in }, receiveValue: { [weak self] isLoggedIn in self?.isLoggedIn = isLoggedIn } diff --git a/Koin/Presentation/Notice/ManageNoticeKeyWord/ManageNoticeKeywordViewModel.swift b/Koin/Presentation/Notice/ManageNoticeKeyWord/ManageNoticeKeywordViewModel.swift index b1120bdaa..6ecc1730b 100644 --- a/Koin/Presentation/Notice/ManageNoticeKeyWord/ManageNoticeKeywordViewModel.swift +++ b/Koin/Presentation/Notice/ManageNoticeKeyWord/ManageNoticeKeywordViewModel.swift @@ -76,26 +76,28 @@ extension ManageNoticeKeywordViewModel { .execute(keyword: requestModel, myKeywords: myKeywords) .receive(on: DispatchQueue.main) .throttle(for: .milliseconds(400), scheduler: RunLoop.main, latest: true) - .sink(receiveCompletion: { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.fetchMyKeyword() - } - }, receiveValue: { [weak self] _, addKeywordResult in - switch addKeywordResult { - case .exceedNumber: - self?.outputSubject.send(.keywordIsIllegal("키워드는 최대 10개까지 추가할 수 있습니다.")) - case .notInRange: - self?.outputSubject.send(.keywordIsIllegal("키워드는 2글자에서 10글자 사이어야 합니다.")) - case .sameKeyword: - self?.outputSubject.send(.keywordIsIllegal("이미 같은 키워드가 존재합니다.")) - case .success: - if isRecommended { self?.makeLogAnalyticsEvent(label: EventParameter.EventLabel.Campus.recommendedKeyword, category: .click, value: keyword) } - self?.fetchMyKeyword() - } - }).store(in: &self.subscriptions) - - } + .sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.fetchMyKeyword() + } + }, + receiveValue: { [weak self] _, addKeywordResult in + switch addKeywordResult { + case .exceedNumber: + self?.outputSubject.send(.keywordIsIllegal("키워드는 최대 10개까지 추가할 수 있습니다.")) + case .notInRange: + self?.outputSubject.send(.keywordIsIllegal("키워드는 2글자에서 10글자 사이어야 합니다.")) + case .sameKeyword: + self?.outputSubject.send(.keywordIsIllegal("이미 같은 키워드가 존재합니다.")) + case .success: + if isRecommended { self?.makeLogAnalyticsEvent(label: EventParameter.EventLabel.Campus.recommendedKeyword, category: .click, value: keyword) } + self?.fetchMyKeyword() + } + } + ).store(in: &self.subscriptions) + + } } private func fetchMyKeyword() { @@ -106,20 +108,18 @@ extension ManageNoticeKeywordViewModel { } private func getMyKeyword(completion: @escaping ([NoticeKeywordDto]) -> Void) { - fetchNotificationKeywordUseCase.execute().sink(receiveCompletion: { completionResult in - if case let .failure(error) = completionResult { - Log.make().error("\(error)") + fetchNotificationKeywordUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { fetchkeywords in + completion(fetchkeywords.0) } - }, receiveValue: { fetchkeywords in - completion(fetchkeywords.0) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func deleteMyKeyword(keyWord: NoticeKeywordDto) { deleteNotificationKeywordUseCase.execute(keyword: keyWord) .sink(receiveCompletion: { [weak self] completion in if case let .failure(error) = completion { - Log.make().error("\(error)") self?.fetchMyKeyword() } }, receiveValue: { [weak self] result in @@ -129,40 +129,40 @@ extension ManageNoticeKeywordViewModel { } private func fetchSubscription() { - fetchNotiListUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] response in - response.subscribes?.forEach { - if $0.type == .articleKeyWord { - self?.outputSubject.send(.updateSwitch(isOn: $0.isPermit ?? false)) + fetchNotiListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + response.subscribes?.forEach { + if $0.type == .articleKeyWord { + self?.outputSubject.send(.updateSwitch(isOn: $0.isPermit ?? false)) + } } } - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func getRecommendedKeyword(keywords: [NoticeKeywordDto]) { - fetchRecommendedKeywordUseCase.execute(filters: keywords).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchRecommendedKeywordUseCase.execute(filters: keywords).sink( + receiveCompletion: { _ in }, + receiveValue: { keywords in + self.outputSubject.send(.updateRecommendedKeyword(keywords.keywords)) } - }, receiveValue: { keywords in - self.outputSubject.send(.updateRecommendedKeyword(keywords.keywords)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func changeNotification(isOn: Bool) { - changeNotiUseCase.execute(method: isOn ? .post : .delete, type: .articleKeyWord).sink(receiveCompletion: { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.outputSubject.send(.showLoginModal) + changeNotiUseCase.execute(method: isOn ? .post : .delete, type: .articleKeyWord).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.outputSubject.send(.showLoginModal) + } + }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.updateSwitch(isOn: isOn)) + let value = isOn ? "on" : "off" + self?.makeLogAnalyticsEvent(label: EventParameter.EventLabel.Campus.keywordNotification, category: .click, value: value) } - }, receiveValue: { [weak self] response in - self?.outputSubject.send(.updateSwitch(isOn: isOn)) - let value = isOn ? "on" : "off" - self?.makeLogAnalyticsEvent(label: EventParameter.EventLabel.Campus.keywordNotification, category: .click, value: value) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Notice/NoticeData/NoticeDataViewModel.swift b/Koin/Presentation/Notice/NoticeData/NoticeDataViewModel.swift index e3e93501f..b966e41d3 100644 --- a/Koin/Presentation/Notice/NoticeData/NoticeDataViewModel.swift +++ b/Koin/Presentation/Notice/NoticeData/NoticeDataViewModel.swift @@ -91,14 +91,16 @@ final class NoticeDataViewModel: ViewModelProtocol { extension NoticeDataViewModel { private func createChatRoom() { - createChatRoomUseCase.execute(articleId: noticeId).sink(receiveCompletion: { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.outputSubject.send(.showToast(error.message)) + createChatRoomUseCase.execute(articleId: noticeId).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.outputSubject.send(.showToast(error.message)) + } + }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.navigateToChat(response.articleId, response.chatRoomId, response.articleTitle)) } - }, receiveValue: { [weak self] response in - self?.outputSubject.send(.navigateToChat(response.articleId, response.chatRoomId, response.articleTitle)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func checkLogin(checkType: CheckType) { @@ -112,60 +114,57 @@ extension NoticeDataViewModel { } func checkAuth() { - checkAuthUseCase.execute().sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + checkAuthUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showAuth(response)) } - }, receiveValue: { [weak self] response in - self?.outputSubject.send(.showAuth(response)) - }).store(in: &subscriptions) - + ).store(in: &subscriptions) } private func fetchLostItem(id: Int) { - fetchLostItemUseCase.execute(id: id).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchLostItemUseCase.execute(id: id).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.type = response.type ?? .lost + self?.outputSubject.send(.updateLostItem(response)) } - }, receiveValue: { [weak self] response in - self?.type = response.type ?? .lost - self?.outputSubject.send(.updateLostItem(response)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } + private func getNoticeData() { outputSubject.send(.updateActivityIndictor(true, nil, nil)) let request = FetchNoticeDataRequest(noticeId: noticeId) - fetchNoticeDataUseCase.execute(request: request).sink(receiveCompletion: { [weak self] completion in - self?.outputSubject.send(.updateActivityIndictor(false, nil, nil)) - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchNoticeDataUseCase.execute(request: request).sink( + receiveCompletion: { [weak self] completion in + self?.outputSubject.send(.updateActivityIndictor(false, nil, nil)) + }, + receiveValue: { [weak self] noticeData in + self?.outputSubject.send(.updateNoticeData(noticeData)) + self?.outputSubject.send(.updateActivityIndictor(false, nil, nil)) } - }, receiveValue: { [weak self] noticeData in - self?.outputSubject.send(.updateNoticeData(noticeData)) - self?.outputSubject.send(.updateActivityIndictor(false, nil, nil)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func getPopularArticle() { - fetchHotNoticeArticlesUseCase.execute(noticeId: noticeId).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchHotNoticeArticlesUseCase.execute(noticeId: noticeId).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] notices in + self?.outputSubject.send(.updatePopularArticles(notices)) } - }, receiveValue: { [weak self] notices in - self?.outputSubject.send(.updatePopularArticles(notices)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func downloadFile(downloadUrl: String, fileName: String) { outputSubject.send(.updateActivityIndictor(true, nil, nil)) - downloadNoticeAttachmentUseCase.execute(downloadUrl: downloadUrl, fileName: fileName).sink(receiveCompletion: { [weak self] completion in - self?.outputSubject.send(.updateActivityIndictor(false, nil, nil)) - if case let .failure(error) = completion { - Log.make().error("\(error)") + downloadNoticeAttachmentUseCase.execute(downloadUrl: downloadUrl, fileName: fileName).sink( + receiveCompletion: { [weak self] completion in + self?.outputSubject.send(.updateActivityIndictor(false, nil, nil)) + }, + receiveValue: { [weak self] downloadedPath in + self?.outputSubject.send(.updateActivityIndictor(false, fileName, downloadedPath)) } - }, receiveValue: { [weak self] downloadedPath in - self?.outputSubject.send(.updateActivityIndictor(false, fileName, downloadedPath)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Notice/NoticeList/NoticeListViewModel.swift b/Koin/Presentation/Notice/NoticeList/NoticeListViewModel.swift index f6abaef5b..14389410d 100644 --- a/Koin/Presentation/Notice/NoticeList/NoticeListViewModel.swift +++ b/Koin/Presentation/Notice/NoticeList/NoticeListViewModel.swift @@ -84,26 +84,23 @@ extension NoticeListViewModel { func checkAuth() { - checkAuthUseCase.execute().sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + checkAuthUseCase.execute().sink( + receiveCompletion: { _ in}, + receiveValue: { [weak self] response in + self?.auth = response.userType } - }, receiveValue: { [weak self] response in - self?.auth = response.userType - }).store(in: &subscriptions) - + ).store(in: &subscriptions) } private func getNoticeInfo(page: Int) { - fetchNoticeArticlesUseCase.execute(boardId: noticeListType.rawValue, keyWord: keyword, page: page, type: fetchType).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchNoticeArticlesUseCase.execute(boardId: noticeListType.rawValue, keyWord: keyword, page: page, type: fetchType).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] articleInfo in + guard let self = self else { return } + self.outputSubject.send(.updateBoard(articleInfo.articles, articleInfo.pages,self.noticeListType)) + self.noticeList = articleInfo.articles } - }, receiveValue: { [weak self] articleInfo in - guard let self = self else { return } - self.outputSubject.send(.updateBoard(articleInfo.articles, articleInfo.pages,self.noticeListType)) - self.noticeList = articleInfo.articles - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func getUserKeywordList(keyword: NoticeKeywordDto? = nil) { @@ -129,16 +126,15 @@ extension NoticeListViewModel { } private func fetchUserKeyword(completion: @escaping ([NoticeKeywordDto]) -> Void) { - fetchMyKeywordUseCase.execute().sink(receiveCompletion: { response in - if case let .failure(error) = response { - Log.make().error("\(error)") - } - }, receiveValue: { [weak self] fetchResult in - if fetchResult.0.isEmpty { - self?.outputSubject.send(.isLogined(fetchResult.1)) + fetchMyKeywordUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] fetchResult in + if fetchResult.0.isEmpty { + self?.outputSubject.send(.isLogined(fetchResult.1)) + } + completion(fetchResult.0) } - completion(fetchResult.0) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Notice/NoticeSearch/NoticeSearchViewModel.swift b/Koin/Presentation/Notice/NoticeSearch/NoticeSearchViewModel.swift index 0972ea4db..22f80448e 100644 --- a/Koin/Presentation/Notice/NoticeSearch/NoticeSearchViewModel.swift +++ b/Koin/Presentation/Notice/NoticeSearch/NoticeSearchViewModel.swift @@ -64,13 +64,12 @@ final class NoticeSearchViewModel: ViewModelProtocol { extension NoticeSearchViewModel { private func getHotKeyWord(count: Int) { - fetchHotKeywordUseCase.execute(count: count).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchHotKeywordUseCase.execute(count: count).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] keyWords in + self?.outputSubject.send(.updateHotKeyWord(keyWords)) } - }, receiveValue: { [weak self] keyWords in - self?.outputSubject.send(.updateHotKeyWord(keyWords)) - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func searchWord(word: String, searchedDate: Date, actionType: Int) { @@ -100,18 +99,17 @@ extension NoticeSearchViewModel { if let keyWord = keyWord { self.keyWord = keyWord } - searchNoticeArticlesUseCase.execute(requestModel: requestModel).sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + searchNoticeArticlesUseCase.execute(requestModel: requestModel).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] articles in + if articles.currentPage == articles.totalPage { + self?.outputSubject.send(.updateSearchedrsult(articles.articles ?? [], true, isNewPage)) + } + else { + self?.outputSubject.send(.updateSearchedrsult(articles.articles ?? [], false, isNewPage)) + } } - }, receiveValue: { [weak self] articles in - if articles.currentPage == articles.totalPage { - self?.outputSubject.send(.updateSearchedrsult(articles.articles ?? [], true, isNewPage)) - } - else { - self?.outputSubject.send(.updateSearchedrsult(articles.articles ?? [], false, isNewPage)) - } - }).store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Setting/ChangeMyProfile/ChangeMyProfileViewModel.swift b/Koin/Presentation/Setting/ChangeMyProfile/ChangeMyProfileViewModel.swift index 57628095a..7be008a69 100644 --- a/Koin/Presentation/Setting/ChangeMyProfile/ChangeMyProfileViewModel.swift +++ b/Koin/Presentation/Setting/ChangeMyProfile/ChangeMyProfileViewModel.swift @@ -92,69 +92,72 @@ extension ChangeMyProfileViewModel { } func checkVerificationCode(phoneNumber: String, code: String) { - checkVerificationCodeUseCase.execute(phoneNumber: phoneNumber, verificationCode: code).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.certNumberMessagePublisher.send((error.message, false)) + checkVerificationCodeUseCase.execute(phoneNumber: phoneNumber, verificationCode: code).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.certNumberMessagePublisher.send((error.message, false)) + } + }, + receiveValue: { [weak self] response in + self?.certNumberMessagePublisher.send(("인증번호가 일치합니다.", true)) + self?.modifyUserData?.phoneNumber = phoneNumber + self?.phoneNumberSuccess = true } - } receiveValue: { [weak self] response in - self?.certNumberMessagePublisher.send(("인증번호가 일치합니다.", true)) - self?.modifyUserData?.phoneNumber = phoneNumber - self?.phoneNumberSuccess = true - }.store(in: &subscriptions) + ).store(in: &subscriptions) } func checkDuplicatedNickname(nickname: String) { - checkDuplicatedNicknameUseCase.execute(nickname: nickname).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - if nickname == self?.userData?.nickname { - - } else { - self?.nicknameMessagePublisher.send((error.message, false)) + checkDuplicatedNicknameUseCase.execute(nickname: nickname).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + if nickname == self?.userData?.nickname { + + } else { + self?.nicknameMessagePublisher.send((error.message, false)) + } } + }, + receiveValue: { [weak self] response in + self?.nicknameMessagePublisher.send(("사용 가능한 닉네임입니다.", true)) + self?.modifyUserData?.nickname = nickname + self?.nicknameSuccess = true } - } receiveValue: { [weak self] response in - self?.nicknameMessagePublisher.send(("사용 가능한 닉네임입니다.", true)) - self?.modifyUserData?.nickname = nickname - self?.nicknameSuccess = true - }.store(in: &subscriptions) + ).store(in: &subscriptions) } func fetchUserData() { - fetchUserDataUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchUserDataUseCase.execute().sink( + receiveCompletion: { _ in}, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showProfile(response)) + self?.userData = response + self?.modifyUserData = response } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showProfile(response)) - self?.userData = response - self?.modifyUserData = response - }.store(in: &subscriptions) + ).store(in: &subscriptions) } func fetchDeptList() { - fetchDeptListUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchDeptListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showDeptDropDownList(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showDeptDropDownList(response)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func modifyProfile(request: UserPutRequest) { - modifyUseCase.execute(requestModel: request).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.outputSubject.send(.showToast(error.message, false, .save)) + modifyUseCase.execute(requestModel: request).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.outputSubject.send(.showToast(error.message, false, .save)) + } + }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showToast("회원 정보 수정이 완료되었습니다.", true, .save)) + self?.userData = response + UserDataManager.shared.setUserData(userData: response) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showToast("회원 정보 수정이 완료되었습니다.", true, .save)) - self?.userData = response - UserDataManager.shared.setUserData(userData: response) - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } private func bind() { diff --git a/Koin/Presentation/Setting/ChangePassword/ChangePasswordViewModel.swift b/Koin/Presentation/Setting/ChangePassword/ChangePasswordViewModel.swift index 133bca630..a4cab9288 100644 --- a/Koin/Presentation/Setting/ChangePassword/ChangePasswordViewModel.swift +++ b/Koin/Presentation/Setting/ChangePassword/ChangePasswordViewModel.swift @@ -71,38 +71,44 @@ final class ChangePasswordViewModel: ViewModelProtocol { extension ChangePasswordViewModel { private func changePassword(password: String) { - changePasswordUseCase.execute(requestModel: .init(newPassword: EncodingWorker().sha256(text: password))).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.outputSubject.send(.showToast(error.message, false, false)) + changePasswordUseCase.execute(requestModel: .init(newPassword: EncodingWorker().sha256(text: password))).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.outputSubject.send(.showToast(error.message, false, false)) + } + }, + receiveValue: { [weak self] _ in + self?.outputSubject.send(.showToast("비밀번호 변경이 완료되었습니다.", true, true)) } - } receiveValue: { [weak self] _ in - self?.outputSubject.send(.showToast("비밀번호 변경이 완료되었습니다.", true, true)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func fetchUserData() { - fetchUserDataUseCase.execute().sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.outputSubject.send(.showToast(error.message, false, true)) + fetchUserDataUseCase.execute().sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.outputSubject.send(.showToast(error.message, false, true)) + } + }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.showEmail(response.loginId ?? "")) + self?.userDto = response } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showEmail(response.loginId ?? "")) - self?.userDto = response - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func checkPassword(password: String) { - checkPasswordUseCase.execute(password: password).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.outputSubject.send(.showErrorMessage(error.message)) + checkPasswordUseCase.execute(password: password).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.outputSubject.send(.showErrorMessage(error.message)) + } + }, + receiveValue: { [weak self] response in + self?.currentStep = 2 + self?.outputSubject.send(.passNextStep) } - } receiveValue: { [weak self] response in - self?.currentStep = 2 - self?.outputSubject.send(.passNextStep) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } } diff --git a/Koin/Presentation/Setting/Settings/SettingsViewModel.swift b/Koin/Presentation/Setting/Settings/SettingsViewModel.swift index cf8c1b74b..55f3bf3ba 100644 --- a/Koin/Presentation/Setting/Settings/SettingsViewModel.swift +++ b/Koin/Presentation/Setting/Settings/SettingsViewModel.swift @@ -55,14 +55,15 @@ final class SettingsViewModel: ViewModelProtocol { extension SettingsViewModel { private func checkLogin(movingScene: MovingScene) { - checkAuthUseCase.execute().sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.outputSubject.send(.showToast(error.message, false, movingScene, nil)) + checkAuthUseCase.execute().sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.outputSubject.send(.showToast(error.message, false, movingScene, nil)) + } + }, receiveValue: { [weak self] response in + self?.outputSubject.send(.showToast("", true, movingScene, response.userType)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.showToast("", true, movingScene, response.userType)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func makeLogAnalyticsEvent(label: EventLabelType, category: EventParameter.EventCategory, value: Any) { diff --git a/Koin/Presentation/Shop/Shop/ShopViewModel.swift b/Koin/Presentation/Shop/Shop/ShopViewModel.swift index 5a0edd2bd..e023f560a 100644 --- a/Koin/Presentation/Shop/Shop/ShopViewModel.swift +++ b/Koin/Presentation/Shop/Shop/ShopViewModel.swift @@ -128,32 +128,27 @@ extension ShopViewModel { } private func fetchShopBenefits() { - fetchShopBenefitUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchShopBenefitUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.updateShopBenefits(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.updateShopBenefits(response)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func fetchBeneficialShops(id: Int) { - fetchBeneficialShopUseCase.execute(id: id).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchBeneficialShopUseCase.execute(id: id).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.updateBeneficialShops(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.updateBeneficialShops(response)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func getShopInfo(id: Int) { - fetchShopListUseCase.execute(requestModel: sortStandard) - .sink(receiveCompletion: { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - }, receiveValue: { [weak self] response in + fetchShopListUseCase.execute(requestModel: sortStandard).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in guard let self = self else { return } let shops = response if self.selectedId != 0 { @@ -162,24 +157,29 @@ extension ShopViewModel { self.outputSubject.send(.changeFilteredShops(shops, self.selectedId)) } self.shopList = response - }).store(in: &subscriptions) + } + ).store(in: &subscriptions) } private func getEventShopList() { - fetchEventListUseCase.execute() - .sink(receiveCompletion: { _ in }, receiveValue: { [weak self] response in + fetchEventListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in guard let self = self else { return } self.outputSubject.send(.updateEventShops(response.events ?? [])) self.getShopInfo(id: self.selectedId) - }).store(in: &subscriptions) + } + ).store(in: &subscriptions) } private func getShopCategory() { - fetchShopCategoryListUseCase.execute() - .sink(receiveCompletion: { _ in }, receiveValue: { [weak self] response in + fetchShopCategoryListUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in self?.categories = response.shopCategories self?.outputSubject.send(.putImage(response)) - }).store(in: &subscriptions) + } + ).store(in: &subscriptions) } func categoryName(for id: Int) -> String { diff --git a/Koin/Presentation/Shop/ShopDetail/ShopDetailViewModel.swift b/Koin/Presentation/Shop/ShopDetail/ShopDetailViewModel.swift index 2aabd005c..c9a965375 100644 --- a/Koin/Presentation/Shop/ShopDetail/ShopDetailViewModel.swift +++ b/Koin/Presentation/Shop/ShopDetail/ShopDetailViewModel.swift @@ -46,14 +46,11 @@ final class ShopDetailViewModel { extension ShopDetailViewModel { private func fetchShopDetail() { - fetchOrderShopDetailFromShopUseCase.execute(shopId: shopId) - .sink(receiveCompletion: { completion in - if case .failure(let failure) = completion { - print("fetchOrdershopDetailFromShop Failed: \(failure)") - } - }, receiveValue: { [weak self] orderShopDetail in + fetchOrderShopDetailFromShopUseCase.execute(shopId: shopId).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] orderShopDetail in self?.outputSubject.send(.update(shopDetail: orderShopDetail)) - }) - .store(in: &subscriptions) + } + ).store(in: &subscriptions) } } diff --git a/Koin/Presentation/Shop/ShopReview/ShopReviewViewModel.swift b/Koin/Presentation/Shop/ShopReview/ShopReviewViewModel.swift index 078cf2ed5..d6613dba0 100644 --- a/Koin/Presentation/Shop/ShopReview/ShopReviewViewModel.swift +++ b/Koin/Presentation/Shop/ShopReview/ShopReviewViewModel.swift @@ -79,13 +79,12 @@ final class ShopReviewViewModel: ViewModelProtocol { } private func updateShopName() { - fetchShopDataUseCase.execute(shopId: shopId) .sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchShopDataUseCase.execute(shopId: shopId).sink( + receiveCompletion: { _ in}, + receiveValue: { [weak self] response in + self?.outputSubject.send(.updateShopName(response.name)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.updateShopName(response.name)) - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func uploadFile(files: [Data]) { diff --git a/Koin/Presentation/Shop/ShopSearch/ShopSearchViewModel.swift b/Koin/Presentation/Shop/ShopSearch/ShopSearchViewModel.swift index 605941a34..013e007b9 100644 --- a/Koin/Presentation/Shop/ShopSearch/ShopSearchViewModel.swift +++ b/Koin/Presentation/Shop/ShopSearch/ShopSearchViewModel.swift @@ -56,15 +56,11 @@ extension ShopSearchViewModel { } private func fetchSearchShop(_ keyword: String) { - fetchSearchShopUseCase.execute(keyword: keyword) - .sink(receiveCompletion: { completion in - if case .failure(let failure) = completion { - print("failed : \(failure)") - } - }, - receiveValue: { [weak self] shopSearchResult in + fetchSearchShopUseCase.execute(keyword: keyword).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] shopSearchResult in self?.outputSubject.send(.update(shopSearchResult)) - }) - .store(in: &subscriptions) + } + ).store(in: &subscriptions) } } diff --git a/Koin/Presentation/Shop/ShopSummary/ShopSummaryViewModel.swift b/Koin/Presentation/Shop/ShopSummary/ShopSummaryViewModel.swift index 3ccff7a84..c010ae535 100644 --- a/Koin/Presentation/Shop/ShopSummary/ShopSummaryViewModel.swift +++ b/Koin/Presentation/Shop/ShopSummary/ShopSummaryViewModel.swift @@ -90,35 +90,35 @@ final class ShopSummaryViewModel { extension ShopSummaryViewModel { private func fetchShopSummary(shopId: Int) { - fetchOrderShopSummaryFromShopUseCase.execute(id: shopId) - .sink(receiveCompletion: { _ in }, - receiveValue: { [weak self] shopSummary in + fetchOrderShopSummaryFromShopUseCase.execute(id: shopId).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] shopSummary in guard let self else { return } self.outputSubject.send(.update1( images: shopSummary.images, name: shopSummary.name, rating: shopSummary.ratingAverage, reviewCount: shopSummary.reviewCount)) - }) - .store(in: &subscriptions) + } + ).store(in: &subscriptions) } private func fetchShopMenusAndGroups(shopId: Int) { - fetchOrderShopMenusAndGroupsFromShopUseCase.execute(shopId: shopId) - .sink(receiveCompletion: { _ in }, - receiveValue: { [weak self] (shopMenusCategory, shopMenus) in + fetchOrderShopMenusAndGroupsFromShopUseCase.execute(shopId: shopId).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] (shopMenusCategory, shopMenus) in guard let self else { return } self.outputSubject.send(.update3( menusGroups: shopMenusCategory, menus: shopMenus)) - }) - .store(in: &subscriptions) + } + ).store(in: &subscriptions) } private func fetchIsAvailable(shopId: Int) { - fetchShopDataUseCase.execute(shopId: shopId) - .sink(receiveCompletion: { _ in }, - receiveValue: { [weak self] in + fetchShopDataUseCase.execute(shopId: shopId).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] in guard let self else { return } self.phonenumber = $0.phone self.outputSubject.send(.update2( @@ -128,8 +128,8 @@ extension ShopSummaryViewModel { maxDeliveryTip: $0.deliveryPrice, description: $0.description, phone: $0.phone)) - }) - .store(in: &subscriptions) + } + ).store(in: &subscriptions) } } diff --git a/Koin/Presentation/TimeTable/Timetable/TimetableViewController.swift b/Koin/Presentation/TimeTable/Timetable/TimetableViewController.swift index 3055aa94c..e337bacc9 100644 --- a/Koin/Presentation/TimeTable/Timetable/TimetableViewController.swift +++ b/Koin/Presentation/TimeTable/Timetable/TimetableViewController.swift @@ -261,26 +261,23 @@ final class TimetableViewController: UIViewController { guard let self = self else { return } if let lectureData = response as? LectureData { - self.viewModel.performLectureModification(lectureData: lectureData) - .sink(receiveCompletion: { _ in - self.viewModel.selectedFrameId = self.viewModel.selectedFrameId - }, receiveValue: { _ in }) - .store(in: &self.subscriptions) - - } else if let customLecture = response as? (String, [Int]) { - self.viewModel.performCustomLectureModification( - lectureName: customLecture.0, - lectureTime: customLecture.1 - ).sink(receiveCompletion: { _ in + self.viewModel.performLectureModification(lectureData: lectureData).sink( + receiveCompletion: { _ in self.viewModel.selectedFrameId = self.viewModel.selectedFrameId - }, receiveValue: { _ in }) - .store(in: &self.subscriptions) - } + }, + receiveValue: { _ in } + ).store(in: &self.subscriptions) + + } else if let customLecture = response as? (String, [Int]) { + self.viewModel.performCustomLectureModification(lectureName: customLecture.0, lectureTime: customLecture.1).sink( + receiveCompletion: { _ in + self.viewModel.selectedFrameId = self.viewModel.selectedFrameId + }, + receiveValue: { _ in } + ).store(in: &self.subscriptions) + } }.store(in: &subscriptions) - - } - } extension TimetableViewController { diff --git a/Koin/Presentation/TimeTable/Timetable/TimetableViewModel.swift b/Koin/Presentation/TimeTable/Timetable/TimetableViewModel.swift index aea47d6eb..cc1a6c97a 100644 --- a/Koin/Presentation/TimeTable/Timetable/TimetableViewModel.swift +++ b/Koin/Presentation/TimeTable/Timetable/TimetableViewModel.swift @@ -216,8 +216,10 @@ extension TimetableViewModel { Publishers.MergeMany(deletePublishers) .collect() - .sink(receiveCompletion: { _ in promise(.success(())) }, - receiveValue: { _ in }) + .sink( + receiveCompletion: { _ in promise(.success(())) }, + receiveValue: { _ in } + ) .store(in: &self.subscriptions) } @@ -266,36 +268,36 @@ extension TimetableViewModel { private func deleteLectureById(lecture: LectureData) { - deleteLecturByIdUseCase.execute(id: lecture.id).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + deleteLecturByIdUseCase.execute(id: lecture.id).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] _ in + self?.lectureData.removeAll { + $0.classTime == lecture.classTime && $0.name == lecture.name && $0.professor == lecture.professor + } } - } receiveValue: { [weak self] _ in - self?.lectureData.removeAll { $0.classTime == lecture.classTime && $0.name == lecture.name && $0.professor == lecture.professor} - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } private func _deleteLecture(_ lecture: LectureData) { - deleteLectureUseCase.execute(frameId: selectedFrameId ?? 0, lectureId: lecture.id) .sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + deleteLectureUseCase.execute(frameId: selectedFrameId ?? 0, lectureId: lecture.id).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] _ in + self?.lectureData.removeAll { + $0.classTime == lecture.classTime && + $0.name == lecture.name && + $0.professor == lecture.professor + } } - } receiveValue: { [weak self] _ in - - self?.lectureData.removeAll { $0.classTime == lecture.classTime && $0.name == lecture.name && $0.professor == $0.professor} - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func fetchFrames() { - fetchFramesUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchFramesUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.frameData = response } - } receiveValue: { [weak self] response in - self?.frameData = response - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } } @@ -311,160 +313,152 @@ extension TimetableViewModel { private func postCustomLecture(lectureName: String, classTime: [Int]) { let request = LectureRequest(timetableFrameID: selectedFrameId ?? 0, timetableLecture: [TimetableLecture(lectureID: nil, classTitle: lectureName, classInfos: [ClassInfo(classTime: classTime, classPlace: "")], professor: "", grades: "0", memo: "no memo")]) - postLectureUseCase.execute(request: request).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + postLectureUseCase.execute(request: request).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.lectureData = response } - } receiveValue: { [weak self] response in - self?.lectureData = response - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func postLecture(lecture: LectureData) { let lectureRequest = LectureRequest(timetableFrameID: selectedFrameId ?? 0, timetableLecture: [TimetableLecture(lectureID: lecture.id, classTitle: lecture.name, classInfos: [ ClassInfo( classTime: lecture.classTime, classPlace: "")], professor: lecture.professor, grades: lecture.grades, memo: "메모메모")]) - postLectureUseCase.execute(request: lectureRequest).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + postLectureUseCase.execute(request: lectureRequest).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.lectureData = response } - } receiveValue: { [weak self] response in - self?.lectureData = response - }.store(in: &subscriptions) + ).store(in: &subscriptions) } // 특정 프레임 id의 모든 강의 조회 private func fetchLecture(frameId: Int) { - fetchLectureUseCase.execute(frameId: frameId).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] response in - guard let self = self else { return } - self.lectureData = response - - for frameData in self.frameData { - for frame in frameData.frame { - if frame.id == frameId { - self.outputSubject.send(.showingSelectedFrame(frameData.semester, frame.timetableName)) + fetchLectureUseCase.execute(frameId: frameId).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + guard let self = self else { return } + self.lectureData = response + + for frameData in self.frameData { + for frame in frameData.frame { + if frame.id == frameId { + self.outputSubject.send(.showingSelectedFrame(frameData.semester, frame.timetableName)) + } } } } - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } // 해당 학기 모든 프레임 조회 ( 처음에 보여줄 학기 시간표 선택 위해 필요 ) private func fetchFrame(semester: String) { - fetchFrameUseCase.execute(semester: semester).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] response in - if let firstMainFrame = response.first(where: { $0.isMain }) ?? response.first { - self?.selectedFrameId = firstMainFrame.id - self?.outputSubject.send(.showingSelectedFrame(semester, firstMainFrame.timetableName)) - } else { - self?.outputSubject.send(.showingSelectedFrame("학기 추가하기", nil)) + fetchFrameUseCase.execute(semester: semester).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + if let firstMainFrame = response.first(where: { $0.isMain }) ?? response.first { + self?.selectedFrameId = firstMainFrame.id + self?.outputSubject.send(.showingSelectedFrame(semester, firstMainFrame.timetableName)) + } else { + self?.outputSubject.send(.showingSelectedFrame("학기 추가하기", nil)) + } } - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } private func fetchMyFrames() { - fetchFramesUseCase.execute().sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] response in - - if let lastSemester = response.first?.semester { - self?.selectedSemester = lastSemester - self?.fetchFrame(semester: lastSemester) - } else { - self?.outputSubject.send(.showingSelectedFrame("학기 추가하기", nil)) + fetchFramesUseCase.execute().sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + if let lastSemester = response.first?.semester { + self?.selectedSemester = lastSemester + self?.fetchFrame(semester: lastSemester) + } else { + self?.outputSubject.send(.showingSelectedFrame("학기 추가하기", nil)) + } } - }.store(in: &subscriptions) + ).store(in: &subscriptions) } // 해당 학기 강의들 조회 private func fetchLectureList(semester: String) { - fetchLectureListUseCase.execute(semester: semester).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + fetchLectureListUseCase.execute(semester: semester).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.outputSubject.send(.updateLectureList(response)) } - } receiveValue: { [weak self] response in - self?.outputSubject.send(.updateLectureList(response)) - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } - } // FrameList extension TimetableViewModel { private func rollbackFrame(id: Int) { - rollbackFrameUseCase.execute(id: id).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") + rollbackFrameUseCase.execute(id: id).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] response in + self?.fetchFrames() } - } receiveValue: { [weak self] response in - self?.fetchFrames() - }.store(in: &subscriptions) + ).store(in: &subscriptions) } // 프레임 1개 삭제 private func deleteFrame(frame: FrameDto) { - deleteFrameUseCase.execute(id: frame.id).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.nextOutputSubject.send(.showToast(error.message)) - } - } receiveValue: { [weak self] in - self?.fetchFrames() -// self?.nextOutputSubject.send(.showToastWithId(frame.timetableName, frame.id)) - if frame.id == self?.selectedFrameId { - self?.fetchMyFrames() + deleteFrameUseCase.execute(id: frame.id).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.nextOutputSubject.send(.showToast(error.message)) + } + }, + receiveValue: { [weak self] in + self?.fetchFrames() +// self?.nextOutputSubject.send(.showToastWithId(frame.timetableName, frame.id)) + if frame.id == self?.selectedFrameId { + self?.fetchMyFrames() + } } - }.store(in: &subscriptions) + ).store(in: &subscriptions) } private func modifyFrame(frame: FrameDto) { - modifyFrameUseCase.execute(frame: frame).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.nextOutputSubject.send(.showToast(error.message)) + modifyFrameUseCase.execute(frame: frame).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.nextOutputSubject.send(.showToast(error.message)) + } + }, + receiveValue: { [weak self] response in + self?.fetchFrames() } - } receiveValue: { [weak self] response in - self?.fetchFrames() - }.store(in: &subscriptions) + ).store(in: &subscriptions) } // 프레임 추가하기 private func createFrame(semester: String) { - createFrameUseCase.execute(semester: semester).sink { [weak self] completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - self?.nextOutputSubject.send(.showToast(error.message)) + createFrameUseCase.execute(semester: semester).sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.nextOutputSubject.send(.showToast(error.message)) + } + }, + receiveValue: { [weak self] response in + self?.fetchFrames() } - } receiveValue: { [weak self] response in - self?.fetchFrames() - }.store(in: &subscriptions) + ).store(in: &subscriptions) } // 해당 학기 삭제하기 private func deleteSemester(semester: String) { - deleteSemesterUseCase.execute(semester: semester).sink { completion in - if case let .failure(error) = completion { - Log.make().error("\(error)") - } - } receiveValue: { [weak self] _ in - self?.fetchFrames() - if semester == self?.selectedSemester { - self?.fetchMyFrames() + deleteSemesterUseCase.execute(semester: semester).sink( + receiveCompletion: { _ in }, + receiveValue: { [weak self] _ in + self?.fetchFrames() + if semester == self?.selectedSemester { + self?.fetchMyFrames() + } } - }.store(in: &subscriptions) - + ).store(in: &subscriptions) } } diff --git a/koin.xcodeproj/project.pbxproj b/koin.xcodeproj/project.pbxproj index f84d5e810..57f1bc537 100644 --- a/koin.xcodeproj/project.pbxproj +++ b/koin.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 0FCD163D2BA80EAA00C23A2F /* KeychainWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FCD163C2BA80EAA00C23A2F /* KeychainWorker.swift */; }; + 7C16AA372F3E0DBE0071ABE6 /* Interceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C16AA362F3E0DBE0071ABE6 /* Interceptor.swift */; }; + 7C16AA392F3E9FBC0071ABE6 /* ErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C16AA382F3E9FBC0071ABE6 /* ErrorViewController.swift */; }; 7C17EEA82EBF4F25008BCA89 /* ShopSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C17EEA72EBF4F25008BCA89 /* ShopSearch.swift */; }; 7C17EEAE2EBF7031008BCA89 /* FetchShopSearchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C17EEAD2EBF7031008BCA89 /* FetchShopSearchRequest.swift */; }; 7C17EEB02EBF708B008BCA89 /* ShopSearchDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C17EEAF2EBF708B008BCA89 /* ShopSearchDto.swift */; }; @@ -28,6 +30,7 @@ 7C372F322F1DB4C300149729 /* LostItemImagesCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C372F302F1DB4C300149729 /* LostItemImagesCollectionViewCell.swift */; }; 7C372F332F1DB4C300149729 /* LostItemImagesCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C372F2F2F1DB4C300149729 /* LostItemImagesCollectionView.swift */; }; 7C372F392F1DCFF800149729 /* ModalViewControllerB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C372F382F1DCFF800149729 /* ModalViewControllerB.swift */; }; + 7C3ECC7A2F41505000EE8F13 /* ErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C3ECC792F41505000EE8F13 /* ErrorResponse.swift */; }; 7C4D5A3C2F01638800B40128 /* BusSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4D5A122F01638800B40128 /* BusSearchViewController.swift */; }; 7C4D5A3D2F01638800B40128 /* BusSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4D5A1D2F01638800B40128 /* BusSearchResultViewModel.swift */; }; 7C4D5A3E2F01638800B40128 /* OneBusTimetableTableViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4D5A362F01638800B40128 /* OneBusTimetableTableViewFooter.swift */; }; @@ -691,7 +694,7 @@ D2CA2DD22C1CAE4E0036F02C /* ShopEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CA2DD12C1CAE4E0036F02C /* ShopEvent.swift */; }; D2D380F92BA72F75009AD5A9 /* LoginRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D380F82BA72F75009AD5A9 /* LoginRequest.swift */; }; D2D380FB2BA73162009AD5A9 /* TokenDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D380FA2BA73162009AD5A9 /* TokenDTO.swift */; }; - D2D380FD2BA7415C009AD5A9 /* ErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D380FC2BA7415C009AD5A9 /* ErrorResponse.swift */; }; + D2D380FD2BA7415C009AD5A9 /* ErrorResponseDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D380FC2BA7415C009AD5A9 /* ErrorResponseDto.swift */; }; D2D4225D2BAAEE0900A8AF04 /* DeptDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4225C2BAAEE0900A8AF04 /* DeptDTO.swift */; }; D2D4225F2BAC2AC600A8AF04 /* UserPutRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4225E2BAC2AC600A8AF04 /* UserPutRequest.swift */; }; D2D422612BAC2D7300A8AF04 /* EncodingWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D422602BAC2D7300A8AF04 /* EncodingWorker.swift */; }; @@ -817,6 +820,8 @@ /* Begin PBXFileReference section */ 0FCD163C2BA80EAA00C23A2F /* KeychainWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainWorker.swift; sourceTree = ""; }; + 7C16AA362F3E0DBE0071ABE6 /* Interceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interceptor.swift; sourceTree = ""; }; + 7C16AA382F3E9FBC0071ABE6 /* ErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorViewController.swift; sourceTree = ""; }; 7C17EEA72EBF4F25008BCA89 /* ShopSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopSearch.swift; sourceTree = ""; }; 7C17EEAD2EBF7031008BCA89 /* FetchShopSearchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchShopSearchRequest.swift; sourceTree = ""; }; 7C17EEAF2EBF708B008BCA89 /* ShopSearchDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopSearchDto.swift; sourceTree = ""; }; @@ -837,6 +842,7 @@ 7C372F2F2F1DB4C300149729 /* LostItemImagesCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemImagesCollectionView.swift; sourceTree = ""; }; 7C372F302F1DB4C300149729 /* LostItemImagesCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemImagesCollectionViewCell.swift; sourceTree = ""; }; 7C372F382F1DCFF800149729 /* ModalViewControllerB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalViewControllerB.swift; sourceTree = ""; }; + 7C3ECC792F41505000EE8F13 /* ErrorResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResponse.swift; sourceTree = ""; }; 7C4D5A0D2F01638800B40128 /* BusAreaSelectdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BusAreaSelectdViewController.swift; sourceTree = ""; }; 7C4D5A0E2F01638800B40128 /* BusAreaSelectedCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BusAreaSelectedCollectionView.swift; sourceTree = ""; }; 7C4D5A0F2F01638800B40128 /* BusAreaSelectedCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BusAreaSelectedCollectionViewCell.swift; sourceTree = ""; }; @@ -1494,7 +1500,7 @@ D2D380E02BA6AC50009AD5A9 /* koin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = koin.entitlements; path = koin/koin.entitlements; sourceTree = ""; }; D2D380F82BA72F75009AD5A9 /* LoginRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRequest.swift; sourceTree = ""; }; D2D380FA2BA73162009AD5A9 /* TokenDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDTO.swift; sourceTree = ""; }; - D2D380FC2BA7415C009AD5A9 /* ErrorResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResponse.swift; sourceTree = ""; }; + D2D380FC2BA7415C009AD5A9 /* ErrorResponseDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResponseDto.swift; sourceTree = ""; }; D2D4225C2BAAEE0900A8AF04 /* DeptDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeptDTO.swift; sourceTree = ""; }; D2D4225E2BAC2AC600A8AF04 /* UserPutRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPutRequest.swift; sourceTree = ""; }; D2D422602BAC2D7300A8AF04 /* EncodingWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodingWorker.swift; sourceTree = ""; }; @@ -1624,6 +1630,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7C16AA352F3DE8F50071ABE6 /* CoreData */ = { + isa = PBXGroup; + children = ( + 8354BE512C7A0566009D4D7A /* CoreDataService.swift */, + ); + path = CoreData; + sourceTree = ""; + }; 7C372ED72F1B7F3100149729 /* LostItem */ = { isa = PBXGroup; children = ( @@ -2094,6 +2108,7 @@ 7CED8ED52EF2D1F900457128 /* BannerViewController.swift */, 7CED8ED72EF2D1F900457128 /* ClubView.swift */, 7CA63A2D2F1B119200226F86 /* LostItemListView.swift */, + 7C16AA382F3E9FBC0071ABE6 /* ErrorViewController.swift */, ); path = SubViews; sourceTree = ""; @@ -3715,7 +3730,7 @@ D23479DD2BDB715700FA91BD /* Timetable */, D23479D82BDB5EEF00FA91BD /* Land */, D208A7A32CA1118D007040E7 /* AbTest */, - D2D380FC2BA7415C009AD5A9 /* ErrorResponse.swift */, + D2D380FC2BA7415C009AD5A9 /* ErrorResponseDto.swift */, D208A7BE2CAB97A9007040E7 /* ForceUpdateResponse.swift */, ); path = Decodable; @@ -4122,6 +4137,7 @@ D2A8F8B02C4946480090C7A4 /* Bus */, D2CA2DCE2C1CAC4B0036F02C /* Shop */, D249CD392C158EEC000813F9 /* Dining */, + 7C3ECC792F41505000EE8F13 /* ErrorResponse.swift */, ); path = Model; sourceTree = ""; @@ -4198,6 +4214,9 @@ D2FB23582BFD72C60098BA2B /* Network */ = { isa = PBXGroup; children = ( + D20AB9462C552E68006BC684 /* NetworkService.swift */, + 83E770F72D05864800EB3F84 /* MockNetworkService.swift */, + 7C16AA362F3E0DBE0071ABE6 /* Interceptor.swift */, D2FB235D2BFDAB980098BA2B /* API */, D2FB235F2BFDABBB0098BA2B /* Router.swift */, ); @@ -4236,8 +4255,6 @@ isa = PBXGroup; children = ( D2D462692D63BA5200C60864 /* ChatService.swift */, - 83E770F72D05864800EB3F84 /* MockNetworkService.swift */, - D20AB9462C552E68006BC684 /* NetworkService.swift */, D20AB9572C553621006BC684 /* NotiService.swift */, D2FB236D2C0410510098BA2B /* LogAnalyticsService.swift */, D29F21C22C4DE27C00994554 /* UserService.swift */, @@ -4249,10 +4266,10 @@ 83DD0D852C4FA84900278ABD /* BusService.swift */, 833D9C442C70672C00982145 /* NoticeListService.swift */, 7C8ADD322F20BEC200F85BDE /* LostItemService.swift */, - 8354BE512C7A0566009D4D7A /* CoreDataService.swift */, D208A7A72CA11221007040E7 /* AbTestService.swift */, D208A7CA2CABB755007040E7 /* CoreService.swift */, D2FB23582BFD72C60098BA2B /* Network */, + 7C16AA352F3DE8F50071ABE6 /* CoreData */, ); path = Service; sourceTree = ""; @@ -4598,6 +4615,7 @@ D208A7C62CABB426007040E7 /* CoreRepository.swift in Sources */, 7C61D3172F213D57005FAC3A /* ChangeLostItemStateUseCase.swift in Sources */, D2FCBE812CFE96600097A388 /* NotificationHandler.swift in Sources */, + 7C16AA372F3E0DBE0071ABE6 /* Interceptor.swift in Sources */, D2A8F8BC2C4A71420090C7A4 /* CoopShopDTO.swift in Sources */, D2B7EBDC2D382D5400EE46B0 /* CheckAuthUseCase.swift in Sources */, D27DD0C92BA608310081FD36 /* UIColor+.swift in Sources */, @@ -5096,7 +5114,7 @@ D20AB96F2C574291006BC684 /* BottomSheetViewController.swift in Sources */, D20AD6CF2DFB45670067760A /* FetchHotClubsUseCase.swift in Sources */, D22C54812C6A5957000826DA /* FileUploadResponse.swift in Sources */, - D2D380FD2BA7415C009AD5A9 /* ErrorResponse.swift in Sources */, + D2D380FD2BA7415C009AD5A9 /* ErrorResponseDto.swift in Sources */, D208A7C92CABB486007040E7 /* CheckVersionUseCase.swift in Sources */, D2FB23512BFD47A40098BA2B /* FetchEventListUseCase.swift in Sources */, 83E770F42D04A21500EB3F84 /* ShuttleRouteDTO.swift in Sources */, @@ -5113,6 +5131,7 @@ D27DD0CA2BA608310081FD36 /* DiningDTO.swift in Sources */, D20AB94E2C553427006BC684 /* NotiRepository.swift in Sources */, 833D9C3E2C705E9300982145 /* FetchNoticeArticlesRequest.swift in Sources */, + 7C3ECC7A2F41505000EE8F13 /* ErrorResponse.swift in Sources */, D2FB23552BFD49520098BA2B /* ShopRepository.swift in Sources */, 833D9C432C7061EC00982145 /* NoticeArticlesInfo.swift in Sources */, 833D9C622C74B9EF00982145 /* FetchHotNoticeArticlesUseCase.swift in Sources */, @@ -5135,6 +5154,7 @@ D20AD6FF2E02A4AB0067760A /* FindIdEmailUseCase.swift in Sources */, D215724E2CEDBEC60061E725 /* FrameDTO.swift in Sources */, 83DD0D842C4FA7FF00278ABD /* DefaultBusRepository.swift in Sources */, + 7C16AA392F3E9FBC0071ABE6 /* ErrorViewController.swift in Sources */, D235829F2BC2F5D600D615DD /* ZoomedImageViewController.swift in Sources */, D249CD262C12DC2C000813F9 /* FetchDiningListUseCase.swift in Sources */, B46B8CC42E76CC6300A8E797 /* FetchOrderShopSummaryFromShopUseCase.swift in Sources */,