From 53fef8b5e4dbef8bc167da92cc2bb61a857f7a15 Mon Sep 17 00:00:00 2001 From: kangddong Date: Mon, 19 Jan 2026 18:47:48 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EA=B2=8C=EC=8B=9C=ED=8C=90=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20/=20=EA=B2=8C=EC=8B=9C=ED=8C=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20/=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=A9=80=ED=8B=B0=ED=8C=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/DI/CommunityDIContainer.swift | 1 + Community/Sources/Data/API/CommunityAPI.swift | 4 +- .../Sources/Data/API/CommunityAPIClient.swift | 75 +++++++++++++------ Community/Sources/Data/DTO/BoardRequest.swift | 11 ++- .../CommunityRepositoryImpl.swift | 46 +++++++++++- .../Presentation/Detail/CommunityDetail.swift | 4 +- .../Write/CommunityWriteView.swift | 20 ++--- .../Write/CommunityWriteViewModel.swift | 49 +++++++++++- .../NetworkImpl/NetworkServiceImpl.swift | 40 ++++------ .../NetworkInterface/MultiPartFormImage.swift | 27 +++++++ .../NetworkServiceInterface.swift | 4 +- MyPage/Sources/Data/MyPageDTO.swift | 5 -- MyPage/Sources/Data/MyPageEndpoint.swift | 9 +-- .../Repositories/MyPageRepositoryImpl.swift | 52 ++++++------- 14 files changed, 230 insertions(+), 117 deletions(-) create mode 100644 Infrastructure/Sources/NetworkInterface/MultiPartFormImage.swift diff --git a/Community/Sources/DI/CommunityDIContainer.swift b/Community/Sources/DI/CommunityDIContainer.swift index 8a05eac..0e092a8 100644 --- a/Community/Sources/DI/CommunityDIContainer.swift +++ b/Community/Sources/DI/CommunityDIContainer.swift @@ -137,6 +137,7 @@ extension CommunityDIContainer: CommunityDetailDependency { public func makeViewModel(boardId: Int) -> any CommunityPresentation.CommunityWriteViewModelProtocol { UpdateBoardViewModel( boardId: boardId, + boardDetailUseCase: container.resolve(BoardDetailUseCase.self), updateBoardUseCase: container.resolve(UpdateBoardUseCase.self) ) } diff --git a/Community/Sources/Data/API/CommunityAPI.swift b/Community/Sources/Data/API/CommunityAPI.swift index ae9c461..0245918 100644 --- a/Community/Sources/Data/API/CommunityAPI.swift +++ b/Community/Sources/Data/API/CommunityAPI.swift @@ -32,9 +32,9 @@ public enum BoardEndpoint: Endpoint { public var path: String { switch self { case .createBoard(let dto): - return dto.imageUrls.isEmpty ? "/boards" : "/boards/with-image" + return dto.hasImage ? "/boards/with-images" : "/boards" case .updateBoard(let id, let dto): - return dto.imageUrls.isEmpty ? "/boards/\(id)" : "/boards/\(id)/with-image" + return dto.hasImage ? "/boards/\(id)/with-images" : "/boards/\(id)" case .boards: return "/boards" case .boardsByCategory: diff --git a/Community/Sources/Data/API/CommunityAPIClient.swift b/Community/Sources/Data/API/CommunityAPIClient.swift index 18b6cbe..f5cd3ba 100644 --- a/Community/Sources/Data/API/CommunityAPIClient.swift +++ b/Community/Sources/Data/API/CommunityAPIClient.swift @@ -22,7 +22,8 @@ public protocol CommunityAPIClientInterface: Sendable { title: String, content: String, category: String, - images: [UIImage] + hasImage: Bool, + multiparts: [MultiPartFormType] ) -> AnyPublisher func updateBoard( @@ -30,7 +31,8 @@ public protocol CommunityAPIClientInterface: Sendable { title: String, content: String, category: String, - images: [UIImage] + hasImage: Bool, + multiparts: [MultiPartFormType] ) -> AnyPublisher func deleteBoard(boardId: Int) -> AnyPublisher @@ -91,31 +93,44 @@ public final class CommunityAPIClient: CommunityAPIClientInterface { title: String, content: String, category: String, - images: [UIImage] + hasImage: Bool, + multiparts: [MultiPartFormType] ) -> AnyPublisher { + var multiparts: [MultiPartFormType] = multiparts + let dto = BoardRequestDTO( title: title, content: content, category: category, - imageUrls: [] // multipart에서는 사용 안함 + hasImage: hasImage ) let endpoint = BoardEndpoint.createBoard(dto) - if images.isEmpty { - // 이미지 없으면 일반 JSON request - return networkService.request(endpoint, responseType: SuccessResponse.self) - .map(\.data) - .eraseToAnyPublisher() - } else { - // 이미지 있으면 multipart upload - return networkService.uploadMultipart( + if hasImage { + // 이미지 있으면 multipart upload (request 필드에 JSON, images 필드에 이미지) + if let body = endpoint.body { + let bodyPart = MultiPartFormType( + data: body, + fiedlName: "request", + mimeType: "application/json" + ) + multiparts.append(bodyPart) + } + + + return networkService.uploadMultipartWithJsonRequest( endpoint, - images: images, + multiparts: multiparts, responseType: SuccessResponse.self ) .map(\.data) .eraseToAnyPublisher() + } else { + // 이미지 없으면 일반 JSON request + return networkService.request(endpoint, responseType: SuccessResponse.self) + .map(\.data) + .eraseToAnyPublisher() } } @@ -124,31 +139,43 @@ public final class CommunityAPIClient: CommunityAPIClientInterface { title: String, content: String, category: String, - images: [UIImage] + hasImage: Bool, + multiparts: [MultiPartFormType] ) -> AnyPublisher { + var multiparts: [MultiPartFormType] = multiparts + let dto = BoardRequestDTO( title: title, content: content, category: category, - imageUrls: [] // multipart에서는 사용 안함 + hasImage: hasImage ) let endpoint = BoardEndpoint.updateBoard(boardId: boardId, body: dto) - if images.isEmpty { - // 이미지 없으면 일반 JSON request - return networkService.request(endpoint, responseType: SuccessResponse.self) - .map(\.data) - .eraseToAnyPublisher() - } else { - // 이미지 있으면 multipart upload - return networkService.uploadMultipart( + if hasImage { + // 이미지 있으면 multipart upload (request 필드에 JSON, images 필드에 이미지) + if let body = endpoint.body { + let bodyPart = MultiPartFormType( + data: body, + fiedlName: "request", + mimeType: "application/json" + ) + multiparts.append(bodyPart) + } + + return networkService.uploadMultipartWithJsonRequest( endpoint, - images: images, + multiparts: multiparts, responseType: SuccessResponse.self ) .map(\.data) .eraseToAnyPublisher() + } else { + // 이미지 없으면 일반 JSON request + return networkService.request(endpoint, responseType: SuccessResponse.self) + .map(\.data) + .eraseToAnyPublisher() } } diff --git a/Community/Sources/Data/DTO/BoardRequest.swift b/Community/Sources/Data/DTO/BoardRequest.swift index c0bfdf0..7a6d34d 100644 --- a/Community/Sources/Data/DTO/BoardRequest.swift +++ b/Community/Sources/Data/DTO/BoardRequest.swift @@ -12,17 +12,22 @@ public struct BoardRequestDTO: Encodable, Sendable { public let title: String public let content: String public let category: String - public let imageUrls: [String] + public let hasImage: Bool + + + private enum CodingKeys: String, CodingKey { + case title, content, category + } public init( title: String, content: String, category: String, - imageUrls: [String] + hasImage: Bool ) { self.title = title self.content = content self.category = category - self.imageUrls = imageUrls + self.hasImage = hasImage } } diff --git a/Community/Sources/Data/Repositories/CommunityRepositoryImpl.swift b/Community/Sources/Data/Repositories/CommunityRepositoryImpl.swift index f6be76a..d038275 100644 --- a/Community/Sources/Data/Repositories/CommunityRepositoryImpl.swift +++ b/Community/Sources/Data/Repositories/CommunityRepositoryImpl.swift @@ -44,12 +44,33 @@ public final class CommunityRepositoryImpl: CommunityRepository { category: BoardCategory, images: [UIImage] ) async throws -> Board { + let hasImage = !images.isEmpty + var multiparts: [MultiPartFormType] = [] + + if hasImage { + for (index, image) in images.enumerated() { + guard let imageData = image.jpegData(compressionQuality: 0.9) else { + throw NSError(domain: "", code: -0000) + } + let fileName = "image_\(index)_\(UUID().uuidString).jpg" + let part = MultiPartFormType( + data: imageData, + fiedlName: "images", + fileName: fileName, + mimeType: "image/jpeg" + ) + + multiparts.append(part) + } + } + return try await apiClient .createBoard( title: title, content: content, category: category.rawValue, - images: images + hasImage: hasImage, + multiparts: multiparts ) .map { $0.toDomain() } .mapError { $0 as Error } @@ -71,13 +92,34 @@ public final class CommunityRepositoryImpl: CommunityRepository { category: CommunityDomain.BoardCategory, images: [UIImage] ) async throws -> CommunityDomain.Board { + let hasImage = !images.isEmpty + var multiparts: [MultiPartFormType] = [] + + if hasImage { + for (index, image) in images.enumerated() { + guard let imageData = image.jpegData(compressionQuality: 0.9) else { + throw NSError(domain: "", code: -0000) + } + let fileName = "image_\(index)_\(UUID().uuidString).jpg" + let part = MultiPartFormType( + data: imageData, + fiedlName: "images", + fileName: fileName, + mimeType: "image/jpeg" + ) + + multiparts.append(part) + } + } + return try await apiClient .updateBoard( boardId: boardId, title: title, content: content, category: category.rawValue, - images: images + hasImage: hasImage, + multiparts: multiparts ) .map { $0.toDomain() } .mapError { $0 as Error } diff --git a/Community/Sources/Presentation/Detail/CommunityDetail.swift b/Community/Sources/Presentation/Detail/CommunityDetail.swift index d6ce55c..1e7ecb4 100644 --- a/Community/Sources/Presentation/Detail/CommunityDetail.swift +++ b/Community/Sources/Presentation/Detail/CommunityDetail.swift @@ -149,9 +149,7 @@ public struct CommunityDetailView: View { Int64(authorId) == currentUserId { NavigationLink( destination: CommunityWriteView( - viewModel: dependency.makeViewModel(boardId: boardId), - title: viewModel.board?.title ?? "", - content: viewModel.board?.content ?? "" + viewModel: dependency.makeViewModel(boardId: boardId) ) ) { Button("수정") { diff --git a/Community/Sources/Presentation/Write/CommunityWriteView.swift b/Community/Sources/Presentation/Write/CommunityWriteView.swift index 7b16621..3eacb82 100644 --- a/Community/Sources/Presentation/Write/CommunityWriteView.swift +++ b/Community/Sources/Presentation/Write/CommunityWriteView.swift @@ -16,9 +16,6 @@ public struct CommunityWriteView: View { @State private var viewModel: CommunityWriteViewModelProtocol @State private var selectedCategory: BoardCategory = .freeTalk - @State private var title: String = "" - @State private var content: String = "" - @State private var characterCount: Int = 0 @FocusState private var focusedField: Field? @State private var photosPickerItems: [PhotosPickerItem] = [] @@ -27,21 +24,14 @@ public struct CommunityWriteView: View { public init( viewModel: CommunityWriteViewModelProtocol, - title: String = "", - content: String = "", - characterCount: Int = 0, maxCharacterCount: Int = 300 ) { self.viewModel = viewModel - - self.title = title - self.content = content - self.characterCount = content.count self.maxCharacterCount = maxCharacterCount } private var isCharacterMax: Bool { - characterCount >= maxCharacterCount + viewModel.content.count >= maxCharacterCount } enum Field: Hashable { @@ -86,8 +76,8 @@ public struct CommunityWriteView: View { focusedField = nil Task { let success = await viewModel.writeBoard( - title: title, - content: content, + title: viewModel.title, + content: viewModel.content, category: selectedCategory ) if success { @@ -169,7 +159,7 @@ public struct CommunityWriteView: View { RequiredText("제목") BottomLineTextField( - title: $title, + title: $viewModel.title, focusedField: $focusedField, field: .title ) @@ -183,7 +173,7 @@ public struct CommunityWriteView: View { BorderTextEditor( maxCharacterCount: maxCharacterCount, - content: $content, + content: $viewModel.content, focusedField: $focusedField, field: .content ) diff --git a/Community/Sources/Presentation/Write/CommunityWriteViewModel.swift b/Community/Sources/Presentation/Write/CommunityWriteViewModel.swift index e8e71a7..017146e 100644 --- a/Community/Sources/Presentation/Write/CommunityWriteViewModel.swift +++ b/Community/Sources/Presentation/Write/CommunityWriteViewModel.swift @@ -13,6 +13,8 @@ import CommunityDomain import Util public protocol CommunityWriteViewModelProtocol { + var title: String { get set } + var content: String { get set } /// 선택된 이미지 목록 var selectedImages: [SelectedImage] { get set } @@ -54,6 +56,8 @@ public protocol CommunityWriteViewModelProtocol { @Observable public final class CommunityWriteViewModel: CommunityWriteViewModelProtocol { + public var title: String = "" + public var content: String = "" // MARK: - Published State (auto-tracked by @Observable) /// 선택된 이미지 목록 public var selectedImages: [SelectedImage] = [] @@ -225,17 +229,56 @@ public final class CommunityWriteViewModel: CommunityWriteViewModelProtocol { @Observable public final class UpdateBoardViewModel: CommunityWriteViewModelProtocol { + public var title: String = "" + public var content: String = "" + /// 최대 이미지 개수 private let maxImages: Int = 5 // MARK: - Dependencies - - private let updateBoardUseCase: UpdateBoardUseCase private let boardId: Int + private let boardDetailUseCase: BoardDetailUseCase + private let updateBoardUseCase: UpdateBoardUseCase - public init(boardId: Int, updateBoardUseCase: UpdateBoardUseCase) { + + public init( + boardId: Int, + boardDetailUseCase: BoardDetailUseCase, + updateBoardUseCase: UpdateBoardUseCase + ) { self.boardId = boardId + self.boardDetailUseCase = boardDetailUseCase self.updateBoardUseCase = updateBoardUseCase + + Task { + let board = try? await boardDetailUseCase.getBoard(boardId: boardId) + self.title = board?.title ?? "" + self.content = board?.content ?? "" + if let imageUrls = board?.imageUrls { + for imageUrl in imageUrls { + let url = URL(string: imageUrl)! + + guard + let imageData = try? Data(contentsOf: url), + let image = UIImage(data: imageData) + else { return } + + let originalSize = imageData.count + let fileName = "image_\(UUID().uuidString).jpg" + self.selectedImages.append( + SelectedImage( + image: image, + originalSize: originalSize, + fileName: fileName + ) + ) + } + } + + + + } + } public var selectedImages: [CommunityDomain.SelectedImage] = [] diff --git a/Infrastructure/Sources/NetworkImpl/NetworkServiceImpl.swift b/Infrastructure/Sources/NetworkImpl/NetworkServiceImpl.swift index cb3d616..cace792 100644 --- a/Infrastructure/Sources/NetworkImpl/NetworkServiceImpl.swift +++ b/Infrastructure/Sources/NetworkImpl/NetworkServiceImpl.swift @@ -87,46 +87,32 @@ public final class NetworkServiceImpl: NetworkServiceInterface { } } - public func uploadMultipart( + public func uploadMultipartWithJsonRequest( _ endpoint: any Endpoint, - images: [UIImage], + multiparts: [MultiPartFormType], responseType: T.Type ) -> AnyPublisher { do { let urlRequest = try endpoint.createURLRequest() - + #if DEBUG logger?.requestLogger(request: urlRequest) #endif - + guard let url = endpoint.createURL() else { throw NetworkError.invalidURL } - + return session.upload( multipartFormData: { multipartFormData in - // 1. JSON body의 텍스트 필드 추가 (title, content, category) - if let body = endpoint.body, - let jsonObject = try? JSONSerialization.jsonObject(with: body) as? [String: Any] { - for (key, value) in jsonObject { - if let stringValue = "\(value)".data(using: .utf8) { - multipartFormData.append(stringValue, withName: key) - } - } - } - - // 2. 이미지 파일 추가 - for (index, image) in images.enumerated() { - // ImageProcessor는 Util 모듈에 있으므로 여기서는 기본 압축 사용 - if let imageData = image.jpegData(compressionQuality: 0.85) { - let fileName = "image_\(index)_\(UUID().uuidString).jpg" - multipartFormData.append( - imageData, - withName: "images", - fileName: fileName, - mimeType: "image/jpeg" - ) - } + // 1. JSON body를 'request' 필드에 application/json으로 추가 + for multipart in multiparts { + multipartFormData.append( + multipart.data, + withName: multipart.fiedlName, + fileName: multipart.fileName, + mimeType: multipart.mimeType + ) } }, to: url, diff --git a/Infrastructure/Sources/NetworkInterface/MultiPartFormImage.swift b/Infrastructure/Sources/NetworkInterface/MultiPartFormImage.swift new file mode 100644 index 0000000..58b9426 --- /dev/null +++ b/Infrastructure/Sources/NetworkInterface/MultiPartFormImage.swift @@ -0,0 +1,27 @@ +// +// MultiPartFormImage.swift +// Infrastructure +// +// Created by 강동영 on 1/19/26. +// + +import Foundation + +public struct MultiPartFormType { + public let data: Data + public let fiedlName: String + public let fileName: String? + public let mimeType: String? + + public init( + data: Data, + fiedlName: String = "images", + fileName: String? = nil, + mimeType: String? = nil/*"image/jpeg"*/ + ) { + self.data = data + self.fiedlName = fiedlName + self.fileName = fileName + self.mimeType = mimeType + } +} diff --git a/Infrastructure/Sources/NetworkInterface/NetworkServiceInterface.swift b/Infrastructure/Sources/NetworkInterface/NetworkServiceInterface.swift index 1d2da3e..49aab99 100644 --- a/Infrastructure/Sources/NetworkInterface/NetworkServiceInterface.swift +++ b/Infrastructure/Sources/NetworkInterface/NetworkServiceInterface.swift @@ -13,9 +13,9 @@ import UIKit public protocol NetworkServiceInterface: Sendable { func request(_ endpoint: any Endpoint, responseType: T.Type) -> AnyPublisher - func uploadMultipart( + func uploadMultipartWithJsonRequest( _ endpoint: any Endpoint, - images: [UIImage], + multiparts: [MultiPartFormType], responseType: T.Type ) -> AnyPublisher } diff --git a/MyPage/Sources/Data/MyPageDTO.swift b/MyPage/Sources/Data/MyPageDTO.swift index 36918b7..8a60e2a 100644 --- a/MyPage/Sources/Data/MyPageDTO.swift +++ b/MyPage/Sources/Data/MyPageDTO.swift @@ -8,11 +8,6 @@ import SharedDomain // MARK: - Request DTOs -struct UpdateProfileRequest: Codable { - let userId: Int - let profileImageURL: String? -} - struct UpdateNicknameRequest: Codable { let nickname: String } diff --git a/MyPage/Sources/Data/MyPageEndpoint.swift b/MyPage/Sources/Data/MyPageEndpoint.swift index 54dfe9b..e117ccf 100644 --- a/MyPage/Sources/Data/MyPageEndpoint.swift +++ b/MyPage/Sources/Data/MyPageEndpoint.swift @@ -12,7 +12,7 @@ import NetworkImpl // MARK: - MyPage Endpoints enum MyPageEndpoint: Endpoint { case authMe - case updateProfile(UpdateProfileRequest) + case updateProfile(userId: Int) case updateNickname(userID: Int, nickname: String) case logout case deleteAccount(provider: String) @@ -28,8 +28,8 @@ enum MyPageEndpoint: Endpoint { switch self { case .authMe: return "/api/v1/auth/me" - case let .updateProfile(param): - return "/api/v1/users/\(param.userId)/profile" + case let .updateProfile(userId): + return "/api/v1/users/\(userId)/profile" case let .updateNickname(id, _): return "/api/v1/users/\(id)/nickname" case .logout: @@ -71,9 +71,6 @@ enum MyPageEndpoint: Endpoint { var body: Data? { switch self { - case .updateProfile(let request): - // TODO: multi-part - return try? JSONEncoder().encode(request.profileImageURL) case let .updateNickname(_, nickname): let request = UpdateNicknameRequest(nickname: nickname) return try? JSONEncoder().encode(request) diff --git a/MyPage/Sources/Data/Repositories/MyPageRepositoryImpl.swift b/MyPage/Sources/Data/Repositories/MyPageRepositoryImpl.swift index 935cf0a..00c3b19 100644 --- a/MyPage/Sources/Data/Repositories/MyPageRepositoryImpl.swift +++ b/MyPage/Sources/Data/Repositories/MyPageRepositoryImpl.swift @@ -61,33 +61,35 @@ public final class MyPageRepositoryImpl: MyPageRepository { let error = NSError(domain: "incorrect user id", code: -1) throw error } - - let request = UpdateProfileRequest(userId: userId, profileImageURL: nil) - let endpoint = MyPageEndpoint.updateProfile(request) - - do { - if let image = image { - // Upload with multipart if image exists - return try await networkService.uploadMultipart( - endpoint, - images: [image], - responseType: SuccessResponse.self - ) - .map { $0.data.profileImageUrl } - .async() - } else { - // Send null request for default image - return try await networkService.request( - endpoint, - responseType: SuccessResponse.self - ) - .map { $0.data.profileImageUrl } - .async() + + let endpoint = MyPageEndpoint.updateProfile(userId: userId) + + if let image = image { + guard let imageData = image.jpegData(compressionQuality: 0.9) else { + throw NSError(domain: "", code: -0000) } - } catch { - throw error + let fileName = "image_\(UUID().uuidString).jpg" + let imagePart = MultiPartFormType( + data: imageData, + fiedlName: "file", + fileName: fileName, + mimeType: "image/jpeg" + ) + + return try await networkService.uploadMultipartWithJsonRequest( + endpoint, + multiparts: [imagePart], + responseType: SuccessResponse.self + ) + .map { $0.data.profileImageUrl } + .async() + } else { + return try await networkService.request( + endpoint, responseType: SuccessResponse.self + ) + .map { $0.data.profileImageUrl } + .async() } - } public func applyDefaultImage() async throws { From 348a551c5fedc1d81ea177c305d1004a0add93b5 Mon Sep 17 00:00:00 2001 From: kangddong Date: Mon, 19 Jan 2026 18:52:52 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20API=20Response=20=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=B0=20paging=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Alarm/Sources/Data/NotificationDTO.swift | 12 ++++++------ Alarm/Sources/Domain/AlarmEntities.swift | 14 +++++++------- .../Sources/Data/DTO/BoardResponse.swift | 2 +- Community/Sources/Domain/Entities/Board.swift | 4 ++-- .../Responses/PagingContent.swift | 19 +++++++++++++++++++ MyPage/Sources/Data/DTO/MyActivitiesDTO.swift | 2 +- .../Presentation/MyActivitiesView.swift | 5 ++++- .../Presentation/MyActivitiesViewModel.swift | 9 +++------ .../Presentation/MyPage/MyPageViewModel.swift | 2 +- 9 files changed, 44 insertions(+), 25 deletions(-) create mode 100644 Infrastructure/Sources/NetworkInterface/Responses/PagingContent.swift diff --git a/Alarm/Sources/Data/NotificationDTO.swift b/Alarm/Sources/Data/NotificationDTO.swift index 811d57e..d64d1d2 100644 --- a/Alarm/Sources/Data/NotificationDTO.swift +++ b/Alarm/Sources/Data/NotificationDTO.swift @@ -10,13 +10,13 @@ import AlarmDomain public struct NotificationListDataDTO: Decodable, Sendable { public let content: [NotificationResponseDTO] - public let lastId: Int? - public let hasNext: Bool + public let netxCursorId: Int? + public let nextPage: Bool private enum CodingKeys: String, CodingKey { case content - case lastId - case hasNext + case netxCursorId + case nextPage } } @@ -24,8 +24,8 @@ extension NotificationListDataDTO { func toDomain() -> NotificationListData { return NotificationListData( content: content.map { $0.toDomain()}, - lastId: lastId, - hasNext: hasNext + netxCursorId: netxCursorId, + nextPage: nextPage ) } } diff --git a/Alarm/Sources/Domain/AlarmEntities.swift b/Alarm/Sources/Domain/AlarmEntities.swift index cc36942..ba46ec6 100644 --- a/Alarm/Sources/Domain/AlarmEntities.swift +++ b/Alarm/Sources/Domain/AlarmEntities.swift @@ -39,18 +39,18 @@ extension [AlarmPayload] { public struct NotificationListData: Sendable { public let content: [AlarmPayload] - public let lastId: Int? - public let hasNext: Bool + public let netxCursorId: Int? + public let nextPage: Bool private enum CodingKeys: String, CodingKey { case content - case lastId - case hasNext + case netxCursorId + case nextPage } - public init(content: [AlarmPayload], lastId: Int?, hasNext: Bool) { + public init(content: [AlarmPayload], netxCursorId: Int?, nextPage: Bool) { self.content = content - self.lastId = lastId - self.hasNext = hasNext + self.netxCursorId = netxCursorId + self.nextPage = nextPage } } diff --git a/Community/Sources/Data/DTO/BoardResponse.swift b/Community/Sources/Data/DTO/BoardResponse.swift index b8577d6..9f0d90a 100644 --- a/Community/Sources/Data/DTO/BoardResponse.swift +++ b/Community/Sources/Data/DTO/BoardResponse.swift @@ -80,7 +80,7 @@ extension BoardListDataDTO { return BoardListData( content: content.map { $0.toDomain() }, nextCursorId: nextCursorId, - hasNextPage: nextPage + nextPage: nextPage ) } } diff --git a/Community/Sources/Domain/Entities/Board.swift b/Community/Sources/Domain/Entities/Board.swift index 9f7e17c..fd59b68 100644 --- a/Community/Sources/Domain/Entities/Board.swift +++ b/Community/Sources/Domain/Entities/Board.swift @@ -152,9 +152,9 @@ public struct BoardListData: Sendable { public let nextCursorId: Int? public let hasNextPage: Bool - public init(content: [Board], nextCursorId: Int?, hasNextPage: Bool) { + public init(content: [Board], nextCursorId: Int?, nextPage: Bool) { self.content = content self.nextCursorId = nextCursorId - self.hasNextPage = hasNextPage + self.hasNextPage = nextPage } } diff --git a/Infrastructure/Sources/NetworkInterface/Responses/PagingContent.swift b/Infrastructure/Sources/NetworkInterface/Responses/PagingContent.swift new file mode 100644 index 0000000..39c9574 --- /dev/null +++ b/Infrastructure/Sources/NetworkInterface/Responses/PagingContent.swift @@ -0,0 +1,19 @@ +// +// PagingContent.swift +// Infrastructure +// +// Created by 강동영 on 1/19/26. +// + + +public struct PagingContent: Decodable, Sendable { + public let content: T + public let nextCursorId: Int? + public let nextPage: Bool + + private enum CodingKeys: String, CodingKey { + case content + case nextCursorId + case nextPage + } +} \ No newline at end of file diff --git a/MyPage/Sources/Data/DTO/MyActivitiesDTO.swift b/MyPage/Sources/Data/DTO/MyActivitiesDTO.swift index 8175244..902837e 100644 --- a/MyPage/Sources/Data/DTO/MyActivitiesDTO.swift +++ b/MyPage/Sources/Data/DTO/MyActivitiesDTO.swift @@ -51,7 +51,7 @@ extension MyBoardsResponseDTO { return BoardListData( content: content.map { $0.toDomain() }, nextCursorId: nextCursorId, - hasNextPage: nextPage + nextPage: nextPage ) } } diff --git a/MyPage/Sources/Presentation/MyActivitiesView.swift b/MyPage/Sources/Presentation/MyActivitiesView.swift index 9bb9d8a..943aa2a 100644 --- a/MyPage/Sources/Presentation/MyActivitiesView.swift +++ b/MyPage/Sources/Presentation/MyActivitiesView.swift @@ -35,6 +35,9 @@ public struct MyActivitiesView: View { } .background(Color.bgG75) } + .task { + await viewModel.loadBoards() + } .toolbar(.hidden, for: .navigationBar) .refreshable { viewModel.refreshCurrentTab() @@ -294,7 +297,7 @@ struct HambugNavigationView: View { // MARK: - Mock UseCases for Preview private final class MockGetMyBoardsUseCase: GetMyBoardsUseCase { func execute(lastId: Int?, limit: Int, order: String) async throws -> BoardListData { - return BoardListData(content: [], nextCursorId: nil, hasNextPage: false) + return BoardListData(content: [], nextCursorId: nil, nextPage: false) } } diff --git a/MyPage/Sources/Presentation/MyActivitiesViewModel.swift b/MyPage/Sources/Presentation/MyActivitiesViewModel.swift index 1e3e35f..8cf64c7 100644 --- a/MyPage/Sources/Presentation/MyActivitiesViewModel.swift +++ b/MyPage/Sources/Presentation/MyActivitiesViewModel.swift @@ -24,7 +24,9 @@ public final class MyActivitiesViewModel { public var isLoadingMoreComments: Bool = false public var errorMessage: String? = nil - public var selectedTab: ActivityTab = .posts + public var selectedTab: ActivityTab = .posts { + didSet { refreshCurrentTab() } + } // MARK: - Dependencies private let getMyBoardsUseCase: GetMyBoardsUseCase @@ -45,11 +47,6 @@ public final class MyActivitiesViewModel { ) { self.getMyBoardsUseCase = getMyBoardsUseCase self.getMyCommentsUseCase = getMyCommentsUseCase - - Task { - await loadBoards() - await loadComments() - } } // MARK: - Public Methods - Boards diff --git a/MyPage/Sources/Presentation/MyPage/MyPageViewModel.swift b/MyPage/Sources/Presentation/MyPage/MyPageViewModel.swift index e6d8b2d..d939137 100644 --- a/MyPage/Sources/Presentation/MyPage/MyPageViewModel.swift +++ b/MyPage/Sources/Presentation/MyPage/MyPageViewModel.swift @@ -35,7 +35,7 @@ public final class MyPageViewModel { do { let response = try await usecase.fetchProfile() isLoading = false - user = user + user = response profileNickName = response.nickname } catch {