This repository was archived by the owner on Sep 15, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathBloggingPromptsServiceRemote.swift
More file actions
159 lines (141 loc) · 7.11 KB
/
BloggingPromptsServiceRemote.swift
File metadata and controls
159 lines (141 loc) · 7.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/// Encapsulates logic to fetch blogging prompts from the remote endpoint.
///
open class BloggingPromptsServiceRemote: ServiceRemoteWordPressComREST {
/// Used to format dates so the time information is omitted.
private static var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
public enum RequestError: Error {
case encodingFailure
}
/// Fetches a number of blogging prompts for the specified site.
/// Note that this method hits wpcom/v2, which means the `WordPressComRestAPI` needs to be initialized with `LocaleKeyV2`.
///
/// - Parameters:
/// - siteID: Used to check which prompts have been answered for the site with given `siteID`.
/// - number: The number of prompts to query. When not specified, this will default to remote implementation.
/// - fromDate: When specified, this will fetch prompts from the given date. When not specified, this will default to remote implementation.
/// - completion: A closure that will be called when the fetch request completes.
open func fetchPrompts(for siteID: NSNumber,
number: Int? = nil,
fromDate: Date? = nil,
completion: @escaping (Result<[RemoteBloggingPrompt], Error>) -> Void) {
let path = path(forEndpoint: "sites/\(siteID)/blogging-prompts", withVersion: ._2_0)
let requestParameter: [String: AnyHashable] = {
var params = [String: AnyHashable]()
if let number = number, number > 0 {
params["number"] = number
}
if let fromDate = fromDate {
// convert to yyyy-MM-dd format, excluding the timezone information.
// the date parameter doesn't need to be timezone-accurate since prompts are grouped by date.
params["from"] = Self.dateFormatter.string(from: fromDate)
}
return params
}()
wordPressComRestApi.GETData(path, parameters: requestParameter as [String: AnyObject]) { result in
switch result {
case .success((let data, _)):
do {
let decoder = JSONDecoder.apiDecoder
// our API decoder assumes that we're converting from snake case.
// revert it to default so the CodingKeys match the actual response keys.
decoder.keyDecodingStrategy = .useDefaultKeys
let response = try decoder.decode([String: [RemoteBloggingPrompt]].self, from: data)
completion(.success(response.values.first ?? []))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
/// Fetches the blogging prompts settings for a given site.
///
/// - Parameters:
/// - siteID: The site ID for the blogging prompts settings.
/// - completion: Closure that will be called when the request completes.
open func fetchSettings(for siteID: NSNumber, completion: @escaping (Result<RemoteBloggingPromptsSettings, Error>) -> Void) {
let path = path(forEndpoint: "sites/\(siteID)/blogging-prompts/settings", withVersion: ._2_0)
wordPressComRestApi.GET(path, parameters: nil) { result, _ in
switch result {
case .success(let responseObject):
do {
let data = try JSONSerialization.data(withJSONObject: responseObject)
let settings = try JSONDecoder().decode(RemoteBloggingPromptsSettings.self, from: data)
completion(.success(settings))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
/// Updates the blogging prompts settings to remote.
///
/// This will return an updated settings object if at least one of the fields is successfully modified.
/// If nothing has changed, it will still be regarded as a successful operation; but nil will be returned.
///
/// - Parameters:
/// - siteID: The site ID of the blogging prompts settings.
/// - settings: The updated settings to upload.
/// - completion: Closure that will be called when the request completes.
open func updateSettings(for siteID: NSNumber,
with settings: RemoteBloggingPromptsSettings,
completion: @escaping (Result<RemoteBloggingPromptsSettings?, Error>) -> Void) {
let path = path(forEndpoint: "sites/\(siteID)/blogging-prompts/settings", withVersion: ._2_0)
var parameters = [String: AnyObject]()
do {
let data = try JSONEncoder().encode(settings)
parameters = try JSONSerialization.jsonObject(with: data) as? [String: AnyObject] ?? [:]
} catch {
completion(.failure(error))
return
}
// The parameter shouldn't be empty at this point.
// If by some chance it is, let's abort and return early. There could be something wrong with the parsing process.
guard !parameters.isEmpty else {
WPKitLogError("Error encoding RemoteBloggingPromptsSettings object: \(settings)")
completion(.failure(RequestError.encodingFailure))
return
}
wordPressComRestApi.POST(path, parameters: parameters) { responseObject, _ in
do {
let data = try JSONSerialization.data(withJSONObject: responseObject)
let response = try JSONDecoder().decode(UpdateBloggingPromptsSettingsResponse.self, from: data)
completion(.success(response.updated))
} catch {
completion(.failure(error))
}
} failure: { error, _ in
completion(.failure(error))
}
}
}
// MARK: - Private helpers
private extension BloggingPromptsServiceRemote {
/// An intermediate object representing the response structure after updating the prompts settings.
///
/// If there is at least one updated field, the remote will return the full `RemoteBloggingPromptsSettings` object in the `updated` key.
/// Otherwise, if no fields are changed, the remote will assign an empty array to the `updated` key.
struct UpdateBloggingPromptsSettingsResponse: Decodable {
let updated: RemoteBloggingPromptsSettings?
private enum CodingKeys: String, CodingKey {
case updated
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// return nil when no fields are changed.
if let _ = try? container.decode(Array.self, forKey: .updated) {
self.updated = nil
return
}
self.updated = try container.decode(RemoteBloggingPromptsSettings.self, forKey: .updated)
}
}
}