Skip to content
This repository has been archived by the owner on Apr 5, 2024. It is now read-only.

Commit

Permalink
Upload update (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
mltbnz committed Feb 28, 2024
1 parent b28c207 commit 9712149
Show file tree
Hide file tree
Showing 67 changed files with 2,687 additions and 2,434 deletions.
21 changes: 11 additions & 10 deletions SettingsFeatureApp/ContentView.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import ComposableArchitecture
import SharedModels
import SettingsFeature
import SwiftUI

struct ContentView: View {
var body: some View {
SettingsView(
store: .init(
initialState: .init(
accountSettingsState: .init(accountSettings: .init(apiToken: "")),
userSettings: .init()
store: Store(initialState: SettingsDomain.State(
accountSettingsState: AccountSettingsDomain.State(
accountSettings: AccountSettings(apiToken: "")
),
reducer: SettingsDomain()
)
userSettings: UserSettings())
) {
SettingsDomain()
}
)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
#Preview {
ContentView()
}
50 changes: 36 additions & 14 deletions WegliKit/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,40 @@ let package = Package(
),
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", branch: "prerelease/1.0"),
.package(url: "https://github.com/mltbnz/composable-core-location", branch: "main"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", .upToNextMajor(from: "1.10.0")),
.package(url: "https://github.com/pointfreeco/swift-custom-dump", .upToNextMajor(from: "0.3.0")),
.package(url: "https://github.com/evgenyneu/keychain-swift.git", .upToNextMajor(from: "19.0.0")),
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.8.4"),
.package(url: "https://github.com/pointfreeco/swiftui-navigation.git", from: "0.7.1"),
.package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.5.0"),
.package(
url: "https://github.com/pointfreeco/swift-composable-architecture",
from: "1.0.0"
),
.package(
url: "https://github.com/mltbnz/composable-core-location",
branch: "main"
),
.package(
url: "https://github.com/pointfreeco/swift-snapshot-testing.git",
.upToNextMajor(
from: "1.10.0"
)
),
.package(
url: "https://github.com/pointfreeco/swift-custom-dump",
.upToNextMajor(
from: "1.0.0"
)
),
.package(
url: "https://github.com/evgenyneu/keychain-swift.git",
.upToNextMajor(
from: "19.0.0"
)
),
.package(
url: "https://github.com/pointfreeco/xctest-dynamic-overlay",
from: "1.0.0"
),
.package(
url: "https://github.com/onevcat/Kingfisher.git",
from: "7.5.0"
),
],
targets: [
.target(
Expand All @@ -80,8 +106,7 @@ let package = Package(
"NoticeListFeature",
.models,
.styleguide,
.tca,
.navigation
.tca
]
),
.target(
Expand Down Expand Up @@ -146,7 +171,6 @@ let package = Package(
.models,
.styleguide,
.tca,
.navigation,
.product(name: "Kingfisher", package: "Kingfisher")
]
),
Expand Down Expand Up @@ -194,10 +218,10 @@ let package = Package(
.target(
name: "NoticeListFeature",
dependencies: [
.apiClient,
.l10n,
.models,
.tca,
.navigation,
"DescriptionFeature",
"ImagesFeature",
.feedbackClient
Expand Down Expand Up @@ -269,7 +293,6 @@ let package = Package(
.styleguide,
.applicationClient,
.tca,
.navigation
],
resources: [.process("Resources")]
),
Expand Down Expand Up @@ -429,6 +452,5 @@ extension Target.Dependency {
static let tca = product(name: "ComposableArchitecture", package: "swift-composable-architecture")
static let locationClient = product(name: "ComposableCoreLocation", package: "composable-core-location")
static let testOverlay = product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay")
static let navigation = product(name: "SwiftUINavigation", package: "swiftui-navigation")
static let customDump = product(name: "CustomDump", package: "swift-custom-dump")
}
13 changes: 12 additions & 1 deletion WegliKit/Sources/ApiClient/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ public struct APIClient {
throw ApiError.tokenUnavailable
}
urlRequest.addValue(token, forHTTPHeaderField: apiTokenKey)
return try await networkDispatcher().dispatch(request: urlRequest)
let data = try await networkDispatcher().dispatch(request: urlRequest)

if let errorRespone = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
throw ApiError(message: "Code \(errorRespone.code): \(errorRespone.message)")
}

return data
}
}

Expand All @@ -36,3 +42,8 @@ extension APIClient {
}

private let apiTokenKey = "X-API-KEY"

struct ErrorResponse: Decodable {
let code: Int
let message: String
}
2 changes: 1 addition & 1 deletion WegliKit/Sources/ApiClient/Endpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ public extension Endpoint {
/// `/api/uploads` endpoint
static let uploads = Self(baseUrl: Endpoints.wegliAPIEndpoint, path: "/api/uploads")
/// `/api/notices/mail` endpoint
static let submitNotices = Self(baseUrl: Endpoints.wegliAPIEndpoint, path: "/api/notices/mail")
static func submitNotices(id: String) -> Self { Self(baseUrl: Endpoints.wegliAPIEndpoint, path: "/api/notices/\(id)/mail") }
}
2 changes: 2 additions & 0 deletions WegliKit/Sources/ApiClient/NetworkDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ public struct NetworkDispatcher {

func dispatch(request: URLRequest) async throws -> Data {
let (data, response) = try await urlSession().data(for: request)

if let response = response as? HTTPURLResponse, !response.isSuccessful {
throw ApiError(error: httpError(response.statusCode))
}

return data
}
}
Expand Down
44 changes: 12 additions & 32 deletions WegliKit/Sources/ApiClient/Services/APIService.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Combine
import Dependencies
import DependenciesMacros
import Foundation
import Helper
import SharedModels
Expand All @@ -14,29 +15,14 @@ extension DependencyValues {


/// A Service to send a single notice and all persisted notices from the weg-li API
@DependencyClient
public struct APIService {
public var getNotices: @Sendable (Bool) async throws -> [Notice]
public var postNotice: @Sendable (NoticeInput) async throws -> Notice
public var upload: @Sendable (PickerImageResult) async throws -> ImageUploadResponse
public var upload: @Sendable (_ id: PickerImageResult.ID, _ imageData: Data?) async throws -> ImageUploadResponse
public var submitNotice: @Sendable (NoticeInput) async throws -> Notice
public var patchNotice: @Sendable (Notice) async throws -> Notice
public var deleteNotice: @Sendable (String) async throws -> Bool

public init(
getNotices: @Sendable @escaping (Bool) async throws -> [Notice],
postNotice: @Sendable @escaping (NoticeInput) async throws -> Notice,
upload: @Sendable @escaping (PickerImageResult) async throws -> ImageUploadResponse,
submitNotice: @Sendable @escaping (NoticeInput) async throws -> Notice,
patchNotice: @Sendable @escaping (Notice) async throws -> Notice,
deleteNotice: @Sendable @escaping (String) async throws -> Bool
) {
self.getNotices = getNotices
self.postNotice = postNotice
self.upload = upload
self.submitNotice = submitNotice
self.patchNotice = patchNotice
self.deleteNotice = deleteNotice
}
}

extension APIService: DependencyKey {
Expand All @@ -55,17 +41,20 @@ extension APIService: DependencyKey {

return try data.decoded(decoder: .noticeDecoder)
},
upload: { imagePickerResult in
let input: ImageUploadInput? = .make(from: imagePickerResult)
let body = try input?.encoded(encoder: .noticeEncoder)
upload: { id, imageData in
guard let data = imageData else {
throw ApiError(message: "no data provided")
}
let input: ImageUploadInput = .make(id: id, data: data)
let body = try input.encoded(encoder: .noticeEncoder)

let request: Request = .post(.uploads, body: body)
let responseData = try await apiClient.send(request)
return try responseData.decoded(decoder: .noticeDecoder)
},
submitNotice: { notice in
let body = try notice.encoded(encoder: .noticeEncoder)
let data = try await apiClient.send(.patch(.submitNotices, body: body))
let data = try await apiClient.send(.patch(.submitNotices(id: notice.id), body: body))

return try data.decoded(decoder: .noticeDecoder)
},
Expand Down Expand Up @@ -94,7 +83,7 @@ extension APIService: TestDependencyKey {
postNotice: { _ in
.mock
},
upload: { _ in
upload: { _, _ in
ImageUploadResponse(
id: 1,
key: "",
Expand Down Expand Up @@ -122,22 +111,13 @@ extension APIService: TestDependencyKey {
postNotice: { _ in
throw ApiError(error: NetworkRequestError.invalidRequest)
},
upload: { _ in
upload: { _, _ in
throw NetworkRequestError.badRequest
},
submitNotice: { _ in throw ApiError(error: NetworkRequestError.decodingError) },
patchNotice: { _ in throw ApiError(error: NetworkRequestError.decodingError) },
deleteNotice: { _ in throw ApiError(error: NetworkRequestError.decodingError) }
)

public static var testValue: APIService = Self(
getNotices: unimplemented("\(Self.self).getNotices"),
postNotice: unimplemented("\(Self.self).postNotice"),
upload: unimplemented("\(Self.self).upload"),
submitNotice: unimplemented("\(Self.self).submitNotice"),
patchNotice: unimplemented("\(Self.self).patchNotice"),
deleteNotice: unimplemented("\(Self.self).deleteNotice")
)
}

extension ApiError {
Expand Down
28 changes: 11 additions & 17 deletions WegliKit/Sources/ApiClient/Services/GoogleCloudService.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Combine
import Dependencies
import DependenciesMacros
import Foundation
import Helper
import SharedModels
Expand All @@ -12,28 +13,25 @@ extension DependencyValues {
}
}

@DependencyClient
public struct GoogleUploadService {
public var upload: @Sendable (URL?, [URLQueryItem]?, Data?, [String: String]) async throws -> Void

public init(upload: @Sendable @escaping (URL?, [URLQueryItem]?, Data?, [String: String]) async throws -> Void) {
self.upload = upload
}
public var upload: @Sendable (
_ url: URL?,
_ body: Data?,
_ headers: [String: String]
) async throws -> Void
}

extension GoogleUploadService: DependencyKey {
public static var liveValue: GoogleUploadService = .live()

static func live(networkDispatcher: NetworkDispatcher = .live) -> Self {
Self(
upload: { url, _, body, headers in
guard let url = url else {
upload: { url, body, headers in
guard let url else {
throw NetworkRequestError.invalidRequest
}
let directUploadURLComponents = URLComponents(
url: url,
resolvingAgainstBaseURL: false
)
var directUploadURLRequest = URLRequest(url: (directUploadURLComponents?.url)!)
var directUploadURLRequest = URLRequest(url: url)
directUploadURLRequest.httpBody = body
directUploadURLRequest.httpMethod = HTTPMethod.put.rawValue
for (key, value) in headers {
Expand All @@ -47,10 +45,6 @@ extension GoogleUploadService: DependencyKey {

extension GoogleUploadService: TestDependencyKey {
public static let noop = Self(
upload: { _, _, _, _ in }
)

public static var testValue: GoogleUploadService = Self(
upload: unimplemented("\(Self.self).upload")
upload: { _, _, _ in }
)
}
7 changes: 0 additions & 7 deletions WegliKit/Sources/ApiClient/Services/Requests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,4 @@ public extension Request {
request.cachePolicy = forceReload ? .reloadIgnoringLocalCacheData : .useProtocolCachePolicy
return request
}

/// Represents a POST ApiRequest to upload images to `/api/uploads`
static func postImageUploadResults(pickerResult: PickerImageResult) -> Self {
let input: ImageUploadInput? = .make(from: pickerResult)
let bodyData = try? JSONEncoder.noticeEncoder.encode(input)
return post(.uploads, body: bodyData)
}
}

0 comments on commit 9712149

Please sign in to comment.