Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 5bc0876

Browse files
authored
Add /wpcom/v2/sites/:siteid/subscribers (#836)
2 parents 94cbe7e + 9174ac4 commit 5bc0876

File tree

9 files changed

+167
-7
lines changed

9 files changed

+167
-7
lines changed

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ let package = Package(
1111
targets: [
1212
.binaryTarget(
1313
name: "WordPressKit",
14-
url: "https://github.com/user-attachments/files/19732825/WordPressKit.zip",
15-
checksum: "0ecd8b19cd00a4ef363ab6e826f92166c1efa4693db8f3218533510a3f951dbb"
14+
url: "https://github.com/user-attachments/files/19949939/WordPressKit.zip",
15+
checksum: "ba06ff0716595023dd6c98b6a5bc74d4abb35bfb668e24026ffd041460f59137"
1616
),
1717
]
1818
)

Sources/CoreAPI/WordPressComRestApi.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ open class WordPressComRestApi: NSObject {
376376
open func perform(
377377
_ method: HTTPRequestBuilder.Method,
378378
URLString: String,
379-
parameters: [String: AnyObject]? = nil,
379+
parameters: [String: Any]? = nil,
380380
fulfilling progress: Progress? = nil
381381
) async -> APIResult<AnyObject> {
382382
await perform(method, URLString: URLString, parameters: parameters, fulfilling: progress) {
@@ -387,7 +387,7 @@ open class WordPressComRestApi: NSObject {
387387
open func perform<T: Decodable>(
388388
_ method: HTTPRequestBuilder.Method,
389389
URLString: String,
390-
parameters: [String: AnyObject]? = nil,
390+
parameters: [String: Any]? = nil,
391391
fulfilling progress: Progress? = nil,
392392
jsonDecoder: JSONDecoder? = nil,
393393
type: T.Type = T.self
@@ -401,7 +401,7 @@ open class WordPressComRestApi: NSObject {
401401
private func perform<T>(
402402
_ method: HTTPRequestBuilder.Method,
403403
URLString: String,
404-
parameters: [String: AnyObject]?,
404+
parameters: [String: Any]?,
405405
fulfilling progress: Progress?,
406406
decoder: @escaping (Data) throws -> T
407407
) async -> APIResult<T> {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Foundation
2+
3+
public struct RemoteSubscriber: Decodable {
4+
public let userID: Int
5+
public let subscriptionID: Int
6+
public let emailAddress: String?
7+
public let dateSubscribed: Date
8+
public let isEmailSubscriber: Bool
9+
public let subscriptionStatus: String?
10+
public let displayName: String?
11+
public let avatar: String?
12+
13+
private enum CodingKeys: String, CodingKey {
14+
case userID = "user_id"
15+
case subscriptionID = "subscription_id"
16+
case emailAddress = "email_address"
17+
case dateSubscribed = "date_subscribed"
18+
case isEmailSubscriber = "is_email_subscriber"
19+
case subscriptionStatus = "subscription_status"
20+
case displayName = "display_name"
21+
case avatar
22+
}
23+
}

Sources/WordPressKit/Services/PeopleServiceRemote.swift

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,91 @@ public class PeopleServiceRemote: ServiceRemoteWordPressComREST {
173173
})
174174
}
175175

176+
public struct SubscribersParameters {
177+
public var sortField: SortField?
178+
public var sortOrder: SortOrder?
179+
public var filters: [Filter]
180+
181+
public enum SortField: String {
182+
case dateSubscribed = "date_subscribed"
183+
case email = "email"
184+
case name = "name"
185+
case plan = "plan"
186+
case subscriptionStatus = "subscription_status"
187+
}
188+
189+
public enum SortOrder: String {
190+
case ascending = "asc"
191+
case descending = "dsc"
192+
}
193+
194+
public protocol Filter: CustomStringConvertible {}
195+
196+
public enum FilterSubscriptionType: String, Filter {
197+
case email = "email_subscriber"
198+
case reader = "reader_subscriber"
199+
case unconfirmed = "unconfirmed_subscriber"
200+
case blocked = "blocked_subscriber"
201+
202+
public var description: String { rawValue }
203+
}
204+
205+
public enum FilterPaymentType: String, Filter {
206+
case free
207+
case paid
208+
209+
public var description: String { rawValue }
210+
}
211+
212+
public init(sortField: SortField? = nil, sortOrder: SortOrder? = nil, filters: [Filter] = []) {
213+
self.sortField = sortField
214+
self.sortOrder = sortOrder
215+
self.filters = filters
216+
}
217+
}
218+
219+
public struct SubscribersResponse: Decodable {
220+
public var total: Int
221+
public var pages: Int
222+
public var page: Int
223+
public var subscribers: [RemoteSubscriber]
224+
}
225+
226+
public func getSubscribers(
227+
siteID: Int,
228+
page: Int? = nil,
229+
perPage: Int? = 25,
230+
parameters: SubscribersParameters = .init()
231+
) async throws -> SubscribersResponse {
232+
let url = self.path(forEndpoint: "sites/\(siteID)/subscribers", withVersion: ._2_0)
233+
var query: [String: Any] = [:]
234+
if let page {
235+
query["page"] = page
236+
}
237+
if let perPage {
238+
query["per_page"] = perPage
239+
}
240+
if let sortField = parameters.sortField {
241+
query["sort"] = sortField.rawValue
242+
}
243+
if let sortOrder = parameters.sortOrder {
244+
query["sort_order"] = sortOrder.rawValue
245+
}
246+
if !parameters.filters.isEmpty {
247+
query["filters"] = parameters.filters.map { $0.description }
248+
}
249+
250+
let decoder = JSONDecoder()
251+
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats
252+
253+
return try await wordPressComRestApi.perform(
254+
.get,
255+
URLString: url,
256+
jsonDecoder: decoder,
257+
type: SubscribersResponse.self
258+
).get().body
259+
}
260+
176261
/// Updates a specified User's Role
177262
///
178263
/// - Parameters:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"total": 1,
3+
"pages": 1,
4+
"page": 1,
5+
"per_page": 100,
6+
"subscribers": [
7+
{
8+
"user_id": 1,
9+
"subscription_id": 2,
10+
"email_address": "[email protected]",
11+
"date_subscribed": "2025-02-28T19:36:36+00:00",
12+
"is_email_subscriber": false,
13+
"subscription_status": "Not subscribed",
14+
"avatar": "https://0.gravatar.com/avatar/example.jpg",
15+
"display_name": "Test",
16+
"url": "http://example.wordpress.com"
17+
}
18+
],
19+
"is_owner_subscribed": true
20+
}

Tests/WordPressKitTests/Tests/JSONLoader.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Foundation
1212
*/
1313
@objc open func loadFile(_ name: String, type: String) -> JSONDictionary? {
1414

15-
let path = Bundle(for: Swift.type(of: self)).path(forResource: name, ofType: type)
15+
let path = JSONLoader.bundle.path(forResource: name, ofType: type)
1616

1717
if let unwrappedPath = path {
1818
return loadFile(unwrappedPath)
@@ -47,4 +47,15 @@ import Foundation
4747
return nil
4848
}
4949
}
50+
51+
public static func data(named name: String, ext: String = "json") throws -> Data {
52+
guard let url = Bundle(for: JSONLoader.self).url(forResource: name, withExtension: ext) else {
53+
throw URLError(.badURL)
54+
}
55+
return try Data(contentsOf: url)
56+
}
57+
58+
private static var bundle: Bundle {
59+
Bundle(for: JSONLoader.self)
60+
}
5061
}

Tests/WordPressKitTests/Tests/MockWordPressComRestApi.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class MockWordPressComRestApi: WordPressComRestApi {
4949
override func perform<T: Decodable>(
5050
_ method: HTTPRequestBuilder.Method,
5151
URLString: String,
52-
parameters: [String: AnyObject]? = nil,
52+
parameters: [String: Any]? = nil,
5353
fulfilling progress: Progress? = nil,
5454
jsonDecoder: JSONDecoder? = nil,
5555
type: T.Type = T.self

Tests/WordPressKitTests/Tests/PeopleServiceRemoteTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,4 +795,17 @@ class PeopleServiceRemoteTests: RemoteTestCase, RESTTestable {
795795
waitForExpectations(timeout: timeout, handler: nil)
796796
}
797797

798+
func testDecodeSubscribersResponse() throws {
799+
let data = try JSONLoader.data(named: "site-subscribers-response")
800+
801+
let decoder = JSONDecoder()
802+
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats
803+
804+
let response = try decoder.decode(PeopleServiceRemote.SubscribersResponse.self, from: data)
805+
806+
XCTAssertEqual(response.total, 1)
807+
808+
let subscriber = try XCTUnwrap(response.subscribers.first)
809+
XCTAssertEqual(subscriber.userID, 1)
810+
}
798811
}

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
0C363D452C41B468004E241D /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 0C363D442C41B468004E241D /* OHHTTPStubs */; };
2525
0C363D472C41B468004E241D /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C363D462C41B468004E241D /* OHHTTPStubsSwift */; };
2626
0C674E302BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */; };
27+
0C8069A72DC03E85008DFC2F /* site-subscribers-response.json in Resources */ = {isa = PBXBuildFile; fileRef = 0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */; };
2728
0C938A062C416789009BA7B2 /* Secret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A052C416789009BA7B2 /* Secret.swift */; };
2829
0C938A092C4167BC009BA7B2 /* NSString+XMLExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C938A072C4167BB009BA7B2 /* NSString+XMLExtensions.h */; settings = {ATTRIBUTES = (Public, ); }; };
2930
0C938A0A2C4167BC009BA7B2 /* NSString+XMLExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A082C4167BB009BA7B2 /* NSString+XMLExtensions.m */; };
@@ -50,6 +51,7 @@
5051
0CCD4C5C2C41700B00B53F9A /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCD4C5B2C41700B00B53F9A /* UIDevice+Extensions.swift */; };
5152
0CCD4C5F2C41711800B53F9A /* NSObject-SafeExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = 0CCD4C5E2C41711800B53F9A /* NSObject-SafeExpectations */; };
5253
0CCD4C622C41712800B53F9A /* wpxmlrpc in Frameworks */ = {isa = PBXBuildFile; productRef = 0CCD4C612C41712800B53F9A /* wpxmlrpc */; };
54+
0CE4E8252DC027AC00056DD9 /* RemoteSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE4E8242DC027AC00056DD9 /* RemoteSubscriber.swift */; };
5355
0CED1FE82B617CF300E6DD52 /* AtomicSiteServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CED1FE72B617CF300E6DD52 /* AtomicSiteServiceRemote.swift */; };
5456
0CED1FEB2B617D7D00E6DD52 /* AtomicLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CED1FEA2B617D7D00E6DD52 /* AtomicLogs.swift */; };
5557
1769DEAA24729AFF00F42EFC /* HomepageSettingsServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */; };
@@ -800,6 +802,7 @@
800802
0C3A2A412A2E7BA500FD91D6 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
801803
0C6183C62C420A3700289E73 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
802804
0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackAIServiceRemote.swift; sourceTree = "<group>"; };
805+
0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-subscribers-response.json"; sourceTree = "<group>"; };
803806
0C938A052C416789009BA7B2 /* Secret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secret.swift; sourceTree = "<group>"; };
804807
0C938A072C4167BB009BA7B2 /* NSString+XMLExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+XMLExtensions.h"; sourceTree = "<group>"; };
805808
0C938A082C4167BB009BA7B2 /* NSString+XMLExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+XMLExtensions.m"; sourceTree = "<group>"; };
@@ -824,6 +827,7 @@
824827
0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blaze-campaigns-search.json"; sourceTree = "<group>"; };
825828
0CB190642A2A7569004D3E80 /* BlazeCampaignsSearchResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsSearchResponse.swift; sourceTree = "<group>"; };
826829
0CCD4C5B2C41700B00B53F9A /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = "<group>"; };
830+
0CE4E8242DC027AC00056DD9 /* RemoteSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteSubscriber.swift; sourceTree = "<group>"; };
827831
0CED1FE72B617CF300E6DD52 /* AtomicSiteServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicSiteServiceRemote.swift; sourceTree = "<group>"; };
828832
0CED1FEA2B617D7D00E6DD52 /* AtomicLogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicLogs.swift; sourceTree = "<group>"; };
829833
1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageSettingsServiceRemote.swift; sourceTree = "<group>"; };
@@ -1866,6 +1870,7 @@
18661870
4A68E3DC294070A7004AC3DC /* RemoteReaderSite.swift */,
18671871
4A68E3E0294076C1004AC3DC /* RemoteReaderSiteInfo.swift */,
18681872
9F3E0B9A208732B2009CB5BA /* RemoteReaderSiteInfoSubscription.swift */,
1873+
0CE4E8242DC027AC00056DD9 /* RemoteSubscriber.swift */,
18691874
4A68E3DE29407100004AC3DC /* RemoteReaderTopic.swift */,
18701875
74E2295D1F1E777B0085F7F2 /* RemoteSharingButton.swift */,
18711876
7430C9C81F192F260051B8E6 /* RemoteSourcePostAttribution.h */,
@@ -2541,6 +2546,7 @@
25412546
9AEAA772215E71C000876E62 /* site-quick-start-success.json */,
25422547
74D67F0C1F15C2D70010C5ED /* site-roles-auth-failure.json */,
25432548
74D67F0D1F15C2D70010C5ED /* site-roles-bad-json-failure.json */,
2549+
0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */,
25442550
74D67F0E1F15C2D70010C5ED /* site-roles-success.json */,
25452551
D8DB404121EF22B500B8238E /* site-segments-multiple.json */,
25462552
D813437721F6D7DC0060D99A /* site-segments-single.json */,
@@ -3030,6 +3036,7 @@
30303036
C738CAF528622953001BE107 /* qrlogin-validate-expired-401.json in Resources */,
30313037
937250EC267A15060086075F /* stats-referrer-mark-as-spam.json in Resources */,
30323038
F3FF8A23279C954100E5C90F /* site-email-followers-get-success.json in Resources */,
3039+
0C8069A72DC03E85008DFC2F /* site-subscribers-response.json in Resources */,
30333040
465F889A263B09BF00F4C950 /* wp-block-editor-v1-settings-success-ThemeJSON.json in Resources */,
30343041
BA9A7F7F24C6895600925E81 /* plugin-directory-jetpack-beta.json in Resources */,
30353042
E6B0461425E5B6F500DF6F4F /* sites-invites-links-generate.json in Resources */,
@@ -3491,6 +3498,7 @@
34913498
3FD634F32BC3AD6200CEDF5E /* Result+Callback.swift in Sources */,
34923499
B5A4822E20AC6C1A009D95F6 /* WPKitLogging.m in Sources */,
34933500
3FE2E97C2BC3A332002CA2E1 /* WordPressComRestApi.swift in Sources */,
3501+
0CE4E8252DC027AC00056DD9 /* RemoteSubscriber.swift in Sources */,
34943502
FE6C673C2BB739950083ECAB /* NSAttributedString+extensions.swift in Sources */,
34953503
7430C9A61F1927180051B8E6 /* ReaderSiteServiceRemote.m in Sources */,
34963504
FEE4EF57272FDD4B003CDA3C /* RemoteCommentV2.swift in Sources */,

0 commit comments

Comments
 (0)