Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Alarm/Sources/Data/NotificationDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ 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
}
}

extension NotificationListDataDTO {
func toDomain() -> NotificationListData {
return NotificationListData(
content: content.map { $0.toDomain()},
lastId: lastId,
hasNext: hasNext
netxCursorId: netxCursorId,
nextPage: nextPage
)
}
}
Expand Down
14 changes: 7 additions & 7 deletions Alarm/Sources/Domain/AlarmEntities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
1 change: 1 addition & 0 deletions Community/Sources/DI/CommunityDIContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
}
Expand Down
4 changes: 2 additions & 2 deletions Community/Sources/Data/API/CommunityAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
75 changes: 51 additions & 24 deletions Community/Sources/Data/API/CommunityAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ public protocol CommunityAPIClientInterface: Sendable {
title: String,
content: String,
category: String,
images: [UIImage]
hasImage: Bool,
multiparts: [MultiPartFormType]
) -> AnyPublisher<BoardResponseDTO, NetworkError>

func updateBoard(
boardId: Int,
title: String,
content: String,
category: String,
images: [UIImage]
hasImage: Bool,
multiparts: [MultiPartFormType]
) -> AnyPublisher<BoardResponseDTO, NetworkError>

func deleteBoard(boardId: Int) -> AnyPublisher<Void, NetworkError>
Expand Down Expand Up @@ -91,31 +93,44 @@ public final class CommunityAPIClient: CommunityAPIClientInterface {
title: String,
content: String,
category: String,
images: [UIImage]
hasImage: Bool,
multiparts: [MultiPartFormType]
) -> AnyPublisher<BoardResponseDTO, NetworkError> {
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<BoardResponseDTO>.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<BoardResponseDTO>.self
)
.map(\.data)
.eraseToAnyPublisher()
} else {
// 이미지 없으면 일반 JSON request
return networkService.request(endpoint, responseType: SuccessResponse<BoardResponseDTO>.self)
.map(\.data)
.eraseToAnyPublisher()
}
}

Expand All @@ -124,31 +139,43 @@ public final class CommunityAPIClient: CommunityAPIClientInterface {
title: String,
content: String,
category: String,
images: [UIImage]
hasImage: Bool,
multiparts: [MultiPartFormType]
) -> AnyPublisher<BoardResponseDTO, NetworkError> {
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<BoardResponseDTO>.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<BoardResponseDTO>.self
)
.map(\.data)
.eraseToAnyPublisher()
} else {
// 이미지 없으면 일반 JSON request
return networkService.request(endpoint, responseType: SuccessResponse<BoardResponseDTO>.self)
.map(\.data)
.eraseToAnyPublisher()
}
}

Expand Down
11 changes: 8 additions & 3 deletions Community/Sources/Data/DTO/BoardRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion Community/Sources/Data/DTO/BoardResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ extension BoardListDataDTO {
return BoardListData(
content: content.map { $0.toDomain() },
nextCursorId: nextCursorId,
hasNextPage: nextPage
nextPage: nextPage
)
}
}
46 changes: 44 additions & 2 deletions Community/Sources/Data/Repositories/CommunityRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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 }
Expand Down
4 changes: 2 additions & 2 deletions Community/Sources/Domain/Entities/Board.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
4 changes: 1 addition & 3 deletions Community/Sources/Presentation/Detail/CommunityDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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("수정") {
Expand Down
20 changes: 5 additions & 15 deletions Community/Sources/Presentation/Write/CommunityWriteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = []
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -169,7 +159,7 @@ public struct CommunityWriteView: View {
RequiredText("제목")

BottomLineTextField(
title: $title,
title: $viewModel.title,
focusedField: $focusedField,
field: .title
)
Expand All @@ -183,7 +173,7 @@ public struct CommunityWriteView: View {

BorderTextEditor(
maxCharacterCount: maxCharacterCount,
content: $content,
content: $viewModel.content,
focusedField: $focusedField,
field: .content
)
Expand Down
Loading
Loading