-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from AlexCornforth/feature/messaging
Added networking and messageing service
- Loading branch information
Showing
10 changed files
with
312 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// | ||
// Message.swift | ||
// Treetracker-Core | ||
// | ||
// Created by Alex Cornforth on 03/04/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct Message: Decodable { | ||
|
||
let messageId: String | ||
let parentMessageId: String? | ||
let from: String | ||
let to: String | ||
let subject: String? | ||
let body: String? | ||
let type: MessageType | ||
let composedAt: String | ||
let videoLink: String? | ||
let survey: SurveyResponse? | ||
let surveyResponse: [String]? | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case messageId = "id" | ||
case parentMessageId = "parent_message_id" | ||
case from | ||
case to | ||
case subject | ||
case body | ||
case type | ||
case composedAt = "composed_at" | ||
case videoLink = "video_link" | ||
case survey | ||
case surveyResponse = "survey_response" | ||
} | ||
} | ||
|
||
// MARK: - Nested Types | ||
public extension Message { | ||
|
||
enum MessageType: String, Decodable { | ||
case message | ||
case announce | ||
case survey | ||
case survey_response | ||
} | ||
|
||
struct SurveyResponse: Decodable { | ||
let surveyResponseId: String | ||
let title: String | ||
let questions: [QuestionResponse] | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case surveyResponseId = "id" | ||
case title | ||
case questions | ||
} | ||
} | ||
|
||
struct QuestionResponse: Decodable { | ||
let prompt: String | ||
let choices: [String] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import Foundation | ||
|
||
protocol APIRequest { | ||
var method: HTTPMethod { get } | ||
var endpoint: Endpoint { get } | ||
associatedtype ResponseType: Decodable | ||
associatedtype Parameters: Encodable | ||
var parameters: Parameters { get } | ||
} | ||
|
||
extension APIRequest { | ||
|
||
func urlRequest(rootURL: URL, headers: [String: String]) -> URLRequest { | ||
|
||
let url = rootURL.appendingPathComponent(endpoint.rawValue) | ||
|
||
var urlRequest = URLRequest(url: url) | ||
urlRequest.httpMethod = method.rawValue | ||
|
||
if encodesParametersInURL { | ||
|
||
let encodedURL: URL? = { | ||
|
||
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) | ||
urlComponents?.queryItems = Mirror(reflecting: parameters) | ||
.children | ||
.map({ (label, value) in | ||
// Map any dates to ISO8601 format | ||
guard let date = value as? Date else { | ||
return (label, value) | ||
} | ||
let dateFormatter = ISO8601DateFormatter() | ||
let formattedDate = dateFormatter.string(from: date) | ||
return (label, formattedDate) | ||
}) | ||
.compactMap({ (label, value) in | ||
// If we need to pass boolean value here we can decide with the API team how best to represent | ||
// This should do for 'most' cases though | ||
guard let label else { | ||
return nil | ||
} | ||
return URLQueryItem(name: label, value: "\(value)") | ||
}) | ||
|
||
return urlComponents?.url | ||
}() | ||
|
||
if let encodedURL { | ||
urlRequest.url = encodedURL | ||
} | ||
|
||
} else { | ||
let jsonEncoder = JSONEncoder() | ||
jsonEncoder.dateEncodingStrategy = .iso8601 | ||
urlRequest.httpBody = try? jsonEncoder.encode(parameters) | ||
} | ||
|
||
for header in headers { | ||
urlRequest.setValue(header.value, forHTTPHeaderField: header.key) | ||
} | ||
|
||
return urlRequest | ||
} | ||
|
||
private var encodesParametersInURL: Bool { | ||
switch method { | ||
case .GET: return true | ||
case .POST: return false | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import Foundation | ||
|
||
protocol APIServiceProtocol { | ||
func performAPIRequest<Request: APIRequest>(request: Request, completion: @escaping (Result<Request.ResponseType, Error>) -> Void) | ||
} | ||
|
||
class APIService: APIServiceProtocol { | ||
|
||
private let rootURL: URL | ||
|
||
init(rootURL: URL) { | ||
self.rootURL = rootURL | ||
} | ||
|
||
private var headers: [String: String] { | ||
return [ | ||
"Content-Type": "application/json" | ||
] | ||
} | ||
|
||
func performAPIRequest<Request: APIRequest>(request: Request, completion: @escaping (Result<Request.ResponseType, Error>) -> Void) { | ||
|
||
let urlRequest = request.urlRequest(rootURL: rootURL, headers: headers) | ||
|
||
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in | ||
|
||
DispatchQueue.main.async { | ||
|
||
if let error = error { | ||
completion(.failure(error)) | ||
return | ||
} | ||
|
||
guard let data else { | ||
completion(.failure(APIServiceError.missingData)) | ||
return | ||
} | ||
|
||
do { | ||
let decodedObject = try JSONDecoder().decode(Request.ResponseType.self, from: data) | ||
completion(.success(decodedObject)) | ||
} catch { | ||
completion(.failure(error)) | ||
} | ||
} | ||
} | ||
|
||
task.resume() | ||
} | ||
} | ||
|
||
// MARK: - Errors | ||
enum APIServiceError: Swift.Error { | ||
case missingRootURL | ||
case missingData | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import Foundation | ||
|
||
enum Endpoint: String { | ||
case messages = "messaging/message" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import Foundation | ||
|
||
enum HTTPMethod: String { | ||
case GET | ||
case POST | ||
} |
33 changes: 33 additions & 0 deletions
33
treetracker-core/Networking/APIRequests/GetMessages/GetMessagesRequest.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// | ||
// GetMessagesRequest.swift | ||
// AWSCore | ||
// | ||
// Created by Alex Cornforth on 03/04/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
struct GetMessagesRequest: APIRequest { | ||
|
||
struct Parameters: Encodable { | ||
let handle: String | ||
let since: Date | ||
let offset: Int | ||
let limit: Int | ||
} | ||
|
||
let endpoint: Endpoint = .messages | ||
let method: HTTPMethod = .GET | ||
typealias ResponseType = GetMessagesResponse | ||
|
||
let parameters: Parameters | ||
|
||
init(walletHandle: String, lastSyncTime: Date, offset: Int = 0, limit: Int = 10) { | ||
self.parameters = Parameters( | ||
handle: walletHandle, | ||
since: lastSyncTime, | ||
offset: offset, | ||
limit: limit | ||
) | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
treetracker-core/Networking/APIRequests/GetMessages/GetMessagesResponse.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// | ||
// GetMessagesResponse.swift | ||
// Pods | ||
// | ||
// Created by Alex Cornforth on 03/04/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
struct GetMessagesResponse: Decodable { | ||
let messages: [Message] | ||
} |
48 changes: 48 additions & 0 deletions
48
treetracker-core/Services/Messaging/MessagingService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// | ||
// MessagingService.swift | ||
// Pods | ||
// | ||
// Created by Alex Cornforth on 03/04/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
public protocol MessagingService { | ||
func getMessages(planter: Planter, completion: @escaping (Result<[Message], Error>) -> Void) | ||
} | ||
|
||
// MARK: - Errors | ||
public enum MessagingServiceError: Swift.Error { | ||
case missingPlanterIdentifier | ||
} | ||
|
||
class RemoteMessagesService: MessagingService { | ||
|
||
private let apiService: APIServiceProtocol | ||
|
||
init(apiService: APIServiceProtocol) { | ||
self.apiService = apiService | ||
} | ||
|
||
func getMessages(planter: Planter, completion: @escaping (Result<[Message], Error>) -> Void) { | ||
|
||
guard let walletHandle = planter.identifier else { | ||
completion(.failure(MessagingServiceError.missingPlanterIdentifier)) | ||
return | ||
} | ||
|
||
let request = GetMessagesRequest( | ||
walletHandle: walletHandle, | ||
lastSyncTime: .distantPast | ||
) | ||
|
||
apiService.performAPIRequest(request: request) { result in | ||
switch result { | ||
case .success(let response): | ||
completion(.success([])) | ||
case .failure(let error): | ||
completion(.failure(error)) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters