Implement remote library metadata sync for iPhone
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import Foundation
|
||||
import VelodyDomain
|
||||
|
||||
public struct RemoteTrackDTO: Codable, Hashable, Sendable {
|
||||
public var trackId: String
|
||||
public var title: String
|
||||
public var artist: String
|
||||
public var durationSeconds: Int
|
||||
public var sha256: String
|
||||
public var assetId: String
|
||||
public var createdAt: String
|
||||
public var updatedAt: String
|
||||
|
||||
public init(
|
||||
trackId: String,
|
||||
title: String,
|
||||
artist: String,
|
||||
durationSeconds: Int,
|
||||
sha256: String,
|
||||
assetId: String,
|
||||
createdAt: String,
|
||||
updatedAt: String
|
||||
) {
|
||||
self.trackId = trackId
|
||||
self.title = title
|
||||
self.artist = artist
|
||||
self.durationSeconds = durationSeconds
|
||||
self.sha256 = sha256
|
||||
self.assetId = assetId
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
|
||||
public var remoteTrack: RemoteTrack {
|
||||
RemoteTrack(
|
||||
trackId: trackId,
|
||||
title: title,
|
||||
artist: artist,
|
||||
durationSeconds: durationSeconds,
|
||||
sha256: sha256,
|
||||
assetId: assetId,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoteLibraryResponseDTO: Codable, Hashable, Sendable {
|
||||
public var tracks: [RemoteTrackDTO]
|
||||
|
||||
public init(tracks: [RemoteTrackDTO]) {
|
||||
self.tracks = tracks
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,10 @@ public protocol VelodyAPIClient: Sendable {
|
||||
|
||||
func fetchSyncBootstrap() async throws -> SyncBootstrapResponse
|
||||
|
||||
func fetchRemoteLibrary(
|
||||
deviceId: String
|
||||
) async throws -> RemoteLibraryResponseDTO
|
||||
|
||||
func prepareUpload(
|
||||
_ payload: UploadPrepareRequest
|
||||
) async throws -> UploadPrepareResponse
|
||||
@@ -106,6 +110,19 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
)
|
||||
}
|
||||
|
||||
public func fetchRemoteLibrary(
|
||||
deviceId: String
|
||||
) async throws -> RemoteLibraryResponseDTO {
|
||||
try await sendRequest(
|
||||
method: "GET",
|
||||
pathComponents: ["api", "v1", "library", "tracks"],
|
||||
queryItems: [
|
||||
URLQueryItem(name: "deviceId", value: deviceId),
|
||||
],
|
||||
responseType: RemoteLibraryResponseDTO.self
|
||||
)
|
||||
}
|
||||
|
||||
public func prepareUpload(
|
||||
_ payload: UploadPrepareRequest
|
||||
) async throws -> UploadPrepareResponse {
|
||||
@@ -139,6 +156,7 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
let request = try buildRequest(
|
||||
method: "PUT",
|
||||
pathComponents: ["api", "v1", "uploads", uploadId, "file"],
|
||||
queryItems: [],
|
||||
bodyData: nil,
|
||||
contentType: mimeType
|
||||
)
|
||||
@@ -174,11 +192,13 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
private func sendRequest<Response: Decodable>(
|
||||
method: String,
|
||||
pathComponents: [String],
|
||||
queryItems: [URLQueryItem] = [],
|
||||
responseType: Response.Type
|
||||
) async throws -> Response {
|
||||
let request = try buildRequest(
|
||||
method: method,
|
||||
pathComponents: pathComponents,
|
||||
queryItems: queryItems,
|
||||
bodyData: nil
|
||||
)
|
||||
|
||||
@@ -188,6 +208,7 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
private func sendRequest<Body: Encodable, Response: Decodable>(
|
||||
method: String,
|
||||
pathComponents: [String],
|
||||
queryItems: [URLQueryItem] = [],
|
||||
body: Body,
|
||||
responseType: Response.Type
|
||||
) async throws -> Response {
|
||||
@@ -202,6 +223,7 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
let request = try buildRequest(
|
||||
method: method,
|
||||
pathComponents: pathComponents,
|
||||
queryItems: queryItems,
|
||||
bodyData: bodyData,
|
||||
contentType: "application/json"
|
||||
)
|
||||
@@ -212,10 +234,14 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
private func buildRequest(
|
||||
method: String,
|
||||
pathComponents: [String],
|
||||
queryItems: [URLQueryItem],
|
||||
bodyData: Data?,
|
||||
contentType: String? = nil
|
||||
) throws -> URLRequest {
|
||||
guard let url = endpointURL(pathComponents: pathComponents) else {
|
||||
guard let url = endpointURL(
|
||||
pathComponents: pathComponents,
|
||||
queryItems: queryItems
|
||||
) else {
|
||||
throw VelodyAPIError.invalidServerURL(environment.baseURL.absoluteString)
|
||||
}
|
||||
|
||||
@@ -284,10 +310,24 @@ public struct URLSessionVelodyAPIClient: VelodyAPIClient {
|
||||
}
|
||||
}
|
||||
|
||||
private func endpointURL(pathComponents: [String]) -> URL? {
|
||||
pathComponents.reduce(environment.baseURL) { partialURL, component in
|
||||
private func endpointURL(
|
||||
pathComponents: [String],
|
||||
queryItems: [URLQueryItem]
|
||||
) -> URL? {
|
||||
let baseURL = pathComponents.reduce(environment.baseURL) { partialURL, component in
|
||||
partialURL.appendingPathComponent(component, isDirectory: false)
|
||||
}
|
||||
|
||||
guard !queryItems.isEmpty else {
|
||||
return baseURL
|
||||
}
|
||||
|
||||
guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
components.queryItems = queryItems
|
||||
return components.url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +380,27 @@ public struct StubVelodyAPIClient: VelodyAPIClient {
|
||||
)
|
||||
}
|
||||
|
||||
public func fetchRemoteLibrary(
|
||||
deviceId: String
|
||||
) async throws -> RemoteLibraryResponseDTO {
|
||||
_ = deviceId
|
||||
|
||||
return RemoteLibraryResponseDTO(
|
||||
tracks: [
|
||||
RemoteTrackDTO(
|
||||
trackId: UUID().uuidString,
|
||||
title: "Velody Remote Placeholder",
|
||||
artist: "Private Library",
|
||||
durationSeconds: 245,
|
||||
sha256: String(repeating: "a", count: 64),
|
||||
assetId: UUID().uuidString,
|
||||
createdAt: ISO8601DateFormatter().string(from: .now),
|
||||
updatedAt: ISO8601DateFormatter().string(from: .now)
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
public func prepareUpload(
|
||||
_ payload: UploadPrepareRequest
|
||||
) async throws -> UploadPrepareResponse {
|
||||
|
||||
Reference in New Issue
Block a user