Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GWL-254] 기록 생성 요청 버그 수정 및 AppCoordinator 연결 #286

Merged
merged 6 commits into from
Dec 8, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class AppCoordinator: AppCoordinating {
}

func start() {
showTabBarFlow()
showSplashFlow()
}

private func showSplashFlow() {
Expand Down
24 changes: 12 additions & 12 deletions iOS/Projects/Core/Network/Sources/MultipartFormData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,31 @@ import Foundation

public struct MultipartFormData {
private let boundary: String
public let imageDataList: [Data]
public let multipartItems: [MultipartItem]

public init(uuid: UUID = UUID(), imageDataList: [Data]) {
boundary = "Boundary-\(uuid.uuidString)"
self.imageDataList = imageDataList
public init(multipartItems: [MultipartItem]) {
boundary = UUID().uuidString
self.multipartItems = multipartItems
}

public func makeBody(imageDataList: [Data], mimeType: String) -> Data {
public func makeBody() -> Data {
let lineBreak = "\r\n"
let boundaryPrefix = "--\(boundary)\(lineBreak)"

var body = Data()

for (index, imageData) in imageDataList.enumerated() {
let imgDataKey = "images"
let filename = "image[\(index)]"
for item in multipartItems {
let imageFieldName = "images"
let filename = "image\(UUID().uuidString)"

body.append(boundaryPrefix)
body.append("Content-Disposition: form-data; name=\"\(imgDataKey)\"; filename=\"\(filename)\"\(lineBreak)")
body.append("Content-Type: \(mimeType)\(lineBreak)\(lineBreak)")
body.append(imageData)
body.append(#"Content-Disposition: form-data; name="\#(imageFieldName)"; filename="\#(filename)"\#(lineBreak)"#)
body.append("Content-Type: \(item.mimeType.rawValue)\(lineBreak)\(lineBreak)")
body.append(item.data)
body.append("\(lineBreak)")
body.append("\(boundaryPrefix)")
}

// insert final boundary
body.append("--\(boundary)--\(lineBreak)")

return body
Expand Down
23 changes: 23 additions & 0 deletions iOS/Projects/Core/Network/Sources/MultipartItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// MultipartItem.swift
// Trinet
//
// Created by 홍승현 on 12/8/23.
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved.
//

import Foundation

public struct MultipartItem {
let data: Data
let mimeType: MimeType

public init(data: Data, mimeType: MimeType) {
self.data = data
self.mimeType = mimeType
}

public enum MimeType: String {
case imagePNG = "image/png"
}
}
45 changes: 0 additions & 45 deletions iOS/Projects/Core/Network/Sources/TNEndPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,48 +75,3 @@ private extension Encodable {
return dictionaryTarget
}
}

// MARK: - MultipartFormData

public struct MultipartFormData {
public var data: Data = .init()

let boundary: UUID

public init(boundary: UUID, data: [Data], mimeType: String = "image/png") {
self.boundary = boundary
self.data = createBody(boundary: boundary.uuidString, imageData: data, mimeType: mimeType)
}

private mutating func createBody(boundary: String, imageData: [Data], mimeType: String) -> Data {
let linebreak = "\r\n"
let boundaryPrefix = "--\(boundary)\(linebreak)"

var body = Data()
// 각 이미지 데이터를 멀티파트 형식으로 추가
for (index, imageData) in imageData.enumerated() {
let imgDataKey = "images"
let filename = "image[\(index)]"

body.append(boundaryPrefix)
body.append("Content-Disposition: form-data; name=\"\(imgDataKey)\"; filename=\"\(filename)\"\(linebreak)")
body.append("Content-Type: \(mimeType)\(linebreak)\(linebreak)")
body.append(imageData)
body.append("\(linebreak)")
body.append("--\(boundary)\(linebreak)")
}

// 종결 바운더리 추가
body.append("--\(boundary)--\(linebreak)")

return body
}
}

private extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
2 changes: 1 addition & 1 deletion iOS/Projects/Core/Network/Sources/TNProvidable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public struct TNProvider<T: TNEndPoint>: TNProvidable {
public func uploadRequest(_ service: T, successStatusCodeRange range: Range<Int> = 200 ..< 300, interceptor: TNRequestInterceptor) async throws -> Data {
guard let multipart = service.multipart else { throw TNError.unknownError }
let request = try interceptor.adapt(service.request(), session: session)
let (data, response) = try await session.upload(for: request, from: multipart.data)
let (data, response) = try await session.upload(for: request, from: multipart.makeBody())
let (retriedData, retriedResponse) = try await interceptor.retry(request, session: session, data: data, response: response, delegate: nil)
try checkStatusCode(retriedResponse, successStatusCodeRange: range)
return retriedData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ private struct ImageUploadEndPoint: TNEndPoint {
.accept("application/json"),
.contentType("multipart/form-data; boundary=\(boundary.uuidString)"),
]

multipart = .init(boundary: boundary, data: data)
multipart = .init(multipartItems: data.map { MultipartItem(data: $0, mimeType: .imagePNG) })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import Combine
import CommonNetworkingKeyManager
import Foundation
import Trinet

Expand All @@ -29,7 +30,7 @@ public struct WorkoutRecordRepository: WorkoutRecordRepositoryRepresentable {
Future<Data, Error> { promise in
Task {
do {
let data = try await provider.request(.init(dataForm: dataForm))
let data = try await provider.request(.init(dataForm: dataForm), interceptor: TNKeychainInterceptor.shared)
promise(.success(data))
} catch {
promise(.failure(error))
Expand All @@ -40,7 +41,7 @@ public struct WorkoutRecordRepository: WorkoutRecordRepositoryRepresentable {
.decode(type: GWResponse<[String: Int]>.self, decoder: jsonDecoder)
.tryMap {
guard let dictionary = $0.data,
let recordID = dictionary["recordId"]
let recordID = dictionary["id"]
else {
throw DataLayerError.noData
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public struct WorkoutDataForm: Encodable {
/// 지도 경로 스크린샷 URL
let imageURL: URL

/// 운동 ID
let workoutID: Int

/// 위도 경도를 한 줄의 문자열로 합친 형태입니다.
///
/// `"위도/경도,위도/경도,..."`형태로 들어갑니다.
Expand All @@ -40,6 +43,7 @@ public struct WorkoutDataForm: Encodable {
case workoutTime
case distance
case calorie
case workoutID = "workoutId"
case imageURL = "mapCapture"
case locations = "gps"
case averageHeartRate = "avgHeartRate"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
//

import Combine
import CombineExtension
import CoreLocation
import Foundation
import Log

// MARK: - WorkoutSessionViewModelDependency

protocol WorkoutSessionViewModelDependency {
var startDate: Date { get }
var workoutTypeCode: WorkoutType { get }
}

// MARK: - WorkoutSessionContainerViewModelInput
Expand Down Expand Up @@ -87,20 +90,18 @@ extension WorkoutSessionContainerViewModel: WorkoutSessionContainerViewModelRepr
.catch { _ in Just(URL(string: "https://gblafytgdduy20857289.cdn.ntruss.com/30ab314b-a59a-44c8-b9c5-44d94b4542f0.png")!) }
.eraseToAnyPublisher()

let recordPublisher = input.endWorkoutPublisher
let recordPublisher = mapURLPublisher
.withLatestFrom(input.locationPublisher) {
$1.map { LocationDTO(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude) }
}
.withLatestFrom(mapURLPublisher) {
return ($0, $1)
return (url: $0, locations: $1.map { LocationDTO(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude) })
}
.withLatestFrom(input.healthPublisher) { [dependency] tuple, health in
let workoutData = WorkoutDataForm(
workoutTime: Int(dependency.startDate.timeIntervalSince1970.rounded(.down)),
distance: Int(health.distance?.rounded(.toNearestOrAwayFromZero) ?? 0),
calorie: Int(health.calorie?.rounded(.toNearestOrAwayFromZero) ?? 0),
imageURL: tuple.1,
locations: tuple.0.map(\.description).joined(separator: ","),
imageURL: tuple.url,
workoutID: dependency.workoutTypeCode.typeCode,
locations: tuple.locations.map(\.description).joined(separator: ","),
averageHeartRate: Int(health.averageHeartRate?.rounded(.toNearestOrAwayFromZero) ?? 0),
minimumHeartRate: Int(health.minimumHeartRate?.rounded(.toNearestOrAwayFromZero) ?? 0),
maximumHeartRate: Int(health.maximumHeartRate?.rounded(.toNearestOrAwayFromZero) ?? 0)
Expand Down Expand Up @@ -130,6 +131,6 @@ extension WorkoutSessionContainerViewModel: WorkoutSessionContainerViewModelRepr
.oneSecondsTimerPublisher()
.map { WorkoutSessionContainerState.updateTime($0) }

return Just(WorkoutSessionContainerState.idle).merge(with: recordErrorPublisher, workoutTimerPublisher).eraseToAnyPublisher()
return Just(.idle).merge(with: recordErrorPublisher, workoutTimerPublisher).eraseToAnyPublisher()
}
}
2 changes: 1 addition & 1 deletion iOS/Projects/Features/SignUp/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let project = Project.makeModule(
targets: .feature(
.signUp,
testingOptions: [.unitTest],
dependencies: [.trinet, .keychain, .combineCocoa, .coordinator, .log, .designSystem, .feature(.login), .commonNetworkingKeyManager],
dependencies: [.trinet, .keychain, .combineCocoa, .coordinator, .log, .designSystem, .commonNetworkingKeyManager],
testDependencies: [],
resources: "Resources/**"
)
Expand Down