Skip to content

Commit

Permalink
Merge pull request #9 from AlexCornforth/feature/messaging
Browse files Browse the repository at this point in the history
Added networking and messageing service
  • Loading branch information
AlexCornforth authored Apr 4, 2023
2 parents eb6ce55 + c8f564d commit 9899aaa
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 3 deletions.
4 changes: 2 additions & 2 deletions Treetracker-Core.podspec
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
Pod::Spec.new do |spec|
spec.name = "Treetracker-Core"
spec.version = "0.0.16"
spec.version = "0.1.0"
spec.license = { :type => "GNU", :file => "LICENSE" }
spec.homepage = "https://github.com/Greenstand/treetracker-ios-core"
spec.summary = "Greenstand Treetracker core library."
spec.platform = :ios, "11.0"
spec.authors = { "Alex Cornforth" => "alexcornforth@me.com" }
spec.swift_version = '5.0'
spec.source = { :git => "https://github.com/Greenstand/treetracker-ios-core.git", :tag => "v0.0.16" }
spec.source = { :git => "https://github.com/Greenstand/treetracker-ios-core.git", :tag => "v0.1.0" }
spec.source_files = "treetracker-core/**/*.{h,m,swift}"
spec.resources = "Resources/*"
spec.dependency "AWSS3", "2.30.4"
Expand Down
65 changes: 65 additions & 0 deletions treetracker-core/Models/Message.swift
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]
}
}
71 changes: 71 additions & 0 deletions treetracker-core/Networking/APIClient/APIRequest.swift
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
}
}
}
56 changes: 56 additions & 0 deletions treetracker-core/Networking/APIClient/APIService.swift
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
}
5 changes: 5 additions & 0 deletions treetracker-core/Networking/APIClient/Endpoint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

enum Endpoint: String {
case messages = "messaging/message"
}
6 changes: 6 additions & 0 deletions treetracker-core/Networking/APIClient/HTTPMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

enum HTTPMethod: String {
case GET
case POST
}
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
)
}
}
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 treetracker-core/Services/Messaging/MessagingService.swift
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))
}
}
}
}
15 changes: 14 additions & 1 deletion treetracker-core/TreetrackerSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ public class TreetrackerSDK: NSObject {
)
}()

private lazy var apiService: APIServiceProtocol = {
return APIService(
rootURL: self.configuration.rootURL
)
}()

private var imageUploadService: ImageUploadService {
return AWSS3ImageUploadService(s3Client: self.awsService)
}
Expand Down Expand Up @@ -138,6 +144,10 @@ public class TreetrackerSDK: NSObject {
return LocalUserDeletionService(coreDataManager: self.coreDataManager)
}

public var messagingService: MessagingService {
return RemoteMessagesService(apiService: self.apiService)
}

// Initializers
public init(configuration: Configuration) {
self.configuration = configuration
Expand Down Expand Up @@ -183,15 +193,18 @@ public extension TreetrackerSDK {
let awsConfiguration: AWSConfiguration
let terms: URL
let defaultTreeImageQuality: DefaultTreeImageQuality
let rootURL: URL

public init(
awsConfiguration: AWSConfiguration,
terms: URL,
defaultTreeImageQuality: DefaultTreeImageQuality
defaultTreeImageQuality: DefaultTreeImageQuality,
rootURL: URL
) {
self.awsConfiguration = awsConfiguration
self.terms = terms
self.defaultTreeImageQuality = defaultTreeImageQuality
self.rootURL = rootURL
}
}
}

0 comments on commit 9899aaa

Please sign in to comment.