Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ let package = Package(
targets: [
.binaryTarget(
name: "WordPressKit",
url: "https://github.com/user-attachments/files/19732825/WordPressKit.zip",
checksum: "0ecd8b19cd00a4ef363ab6e826f92166c1efa4693db8f3218533510a3f951dbb"
url: "https://github.com/user-attachments/files/19949939/WordPressKit.zip",
checksum: "ba06ff0716595023dd6c98b6a5bc74d4abb35bfb668e24026ffd041460f59137"
),
]
)
6 changes: 3 additions & 3 deletions Sources/CoreAPI/WordPressComRestApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ open class WordPressComRestApi: NSObject {
open func perform(
_ method: HTTPRequestBuilder.Method,
URLString: String,
parameters: [String: AnyObject]? = nil,
parameters: [String: Any]? = nil,
fulfilling progress: Progress? = nil
) async -> APIResult<AnyObject> {
await perform(method, URLString: URLString, parameters: parameters, fulfilling: progress) {
Expand All @@ -387,7 +387,7 @@ open class WordPressComRestApi: NSObject {
open func perform<T: Decodable>(
_ method: HTTPRequestBuilder.Method,
URLString: String,
parameters: [String: AnyObject]? = nil,
parameters: [String: Any]? = nil,
fulfilling progress: Progress? = nil,
jsonDecoder: JSONDecoder? = nil,
type: T.Type = T.self
Expand All @@ -401,7 +401,7 @@ open class WordPressComRestApi: NSObject {
private func perform<T>(
_ method: HTTPRequestBuilder.Method,
URLString: String,
parameters: [String: AnyObject]?,
parameters: [String: Any]?,
fulfilling progress: Progress?,
decoder: @escaping (Data) throws -> T
) async -> APIResult<T> {
Expand Down
23 changes: 23 additions & 0 deletions Sources/WordPressKit/Models/RemoteSubscriber.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation

public struct RemoteSubscriber: Decodable {
public let userID: Int
public let subscriptionID: Int
public let emailAddress: String?
public let dateSubscribed: Date
public let isEmailSubscriber: Bool
public let subscriptionStatus: String?
public let displayName: String?
public let avatar: String?

private enum CodingKeys: String, CodingKey {
case userID = "user_id"
case subscriptionID = "subscription_id"
case emailAddress = "email_address"
case dateSubscribed = "date_subscribed"
case isEmailSubscriber = "is_email_subscriber"
case subscriptionStatus = "subscription_status"
case displayName = "display_name"
case avatar
}
}
85 changes: 85 additions & 0 deletions Sources/WordPressKit/Services/PeopleServiceRemote.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,91 @@ public class PeopleServiceRemote: ServiceRemoteWordPressComREST {
})
}

public struct SubscribersParameters {
public var sortField: SortField?
public var sortOrder: SortOrder?
public var filters: [Filter]

public enum SortField: String {
case dateSubscribed = "date_subscribed"
case email = "email"
case name = "name"
case plan = "plan"
case subscriptionStatus = "subscription_status"
}

public enum SortOrder: String {
case ascending = "asc"
case descending = "dsc"
}

public protocol Filter: CustomStringConvertible {}

public enum FilterSubscriptionType: String, Filter {
case email = "email_subscriber"
case reader = "reader_subscriber"
case unconfirmed = "unconfirmed_subscriber"
case blocked = "blocked_subscriber"

public var description: String { rawValue }
}

public enum FilterPaymentType: String, Filter {
case free
case paid

public var description: String { rawValue }
}

public init(sortField: SortField? = nil, sortOrder: SortOrder? = nil, filters: [Filter] = []) {
self.sortField = sortField
self.sortOrder = sortOrder
self.filters = filters
}
}

public struct SubscribersResponse: Decodable {
public var total: Int
public var pages: Int
public var page: Int
public var subscribers: [RemoteSubscriber]
}

public func getSubscribers(
siteID: Int,
page: Int? = nil,
perPage: Int? = 25,
parameters: SubscribersParameters = .init()
) async throws -> SubscribersResponse {
let url = self.path(forEndpoint: "sites/\(siteID)/subscribers", withVersion: ._2_0)
var query: [String: Any] = [:]
if let page {
query["page"] = page
}
if let perPage {
query["per_page"] = perPage
}
if let sortField = parameters.sortField {
query["sort"] = sortField.rawValue
}
if let sortOrder = parameters.sortOrder {
query["sort_order"] = sortOrder.rawValue
}
if !parameters.filters.isEmpty {
query["filters"] = parameters.filters.map { $0.description }
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats

return try await wordPressComRestApi.perform(
.get,
URLString: url,
jsonDecoder: decoder,
type: SubscribersResponse.self
).get().body
}

/// Updates a specified User's Role
///
/// - Parameters:
Expand Down
20 changes: 20 additions & 0 deletions Tests/WordPressKitTests/Mock Data/site-subscribers-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"total": 1,
"pages": 1,
"page": 1,
"per_page": 100,
"subscribers": [
{
"user_id": 1,
"subscription_id": 2,
"email_address": "[email protected]",
"date_subscribed": "2025-02-28T19:36:36+00:00",
"is_email_subscriber": false,
"subscription_status": "Not subscribed",
"avatar": "https://0.gravatar.com/avatar/example.jpg",
"display_name": "Test",
"url": "http://example.wordpress.com"
}
],
"is_owner_subscribed": true
}
13 changes: 12 additions & 1 deletion Tests/WordPressKitTests/Tests/JSONLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Foundation
*/
@objc open func loadFile(_ name: String, type: String) -> JSONDictionary? {

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

if let unwrappedPath = path {
return loadFile(unwrappedPath)
Expand Down Expand Up @@ -47,4 +47,15 @@ import Foundation
return nil
}
}

public static func data(named name: String, ext: String = "json") throws -> Data {
guard let url = Bundle(for: JSONLoader.self).url(forResource: name, withExtension: ext) else {
throw URLError(.badURL)
}
return try Data(contentsOf: url)
}

private static var bundle: Bundle {
Bundle(for: JSONLoader.self)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class MockWordPressComRestApi: WordPressComRestApi {
override func perform<T: Decodable>(
_ method: HTTPRequestBuilder.Method,
URLString: String,
parameters: [String: AnyObject]? = nil,
parameters: [String: Any]? = nil,
fulfilling progress: Progress? = nil,
jsonDecoder: JSONDecoder? = nil,
type: T.Type = T.self
Expand Down
13 changes: 13 additions & 0 deletions Tests/WordPressKitTests/Tests/PeopleServiceRemoteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -795,4 +795,17 @@ class PeopleServiceRemoteTests: RemoteTestCase, RESTTestable {
waitForExpectations(timeout: timeout, handler: nil)
}

func testDecodeSubscribersResponse() throws {
let data = try JSONLoader.data(named: "site-subscribers-response")

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.supportMultipleDateFormats

let response = try decoder.decode(PeopleServiceRemote.SubscribersResponse.self, from: data)

XCTAssertEqual(response.total, 1)

let subscriber = try XCTUnwrap(response.subscribers.first)
XCTAssertEqual(subscriber.userID, 1)
}
}
8 changes: 8 additions & 0 deletions WordPressKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
0C363D452C41B468004E241D /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 0C363D442C41B468004E241D /* OHHTTPStubs */; };
0C363D472C41B468004E241D /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C363D462C41B468004E241D /* OHHTTPStubsSwift */; };
0C674E302BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */; };
0C8069A72DC03E85008DFC2F /* site-subscribers-response.json in Resources */ = {isa = PBXBuildFile; fileRef = 0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */; };
0C938A062C416789009BA7B2 /* Secret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A052C416789009BA7B2 /* Secret.swift */; };
0C938A092C4167BC009BA7B2 /* NSString+XMLExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C938A072C4167BB009BA7B2 /* NSString+XMLExtensions.h */; settings = {ATTRIBUTES = (Public, ); }; };
0C938A0A2C4167BC009BA7B2 /* NSString+XMLExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C938A082C4167BB009BA7B2 /* NSString+XMLExtensions.m */; };
Expand All @@ -50,6 +51,7 @@
0CCD4C5C2C41700B00B53F9A /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCD4C5B2C41700B00B53F9A /* UIDevice+Extensions.swift */; };
0CCD4C5F2C41711800B53F9A /* NSObject-SafeExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = 0CCD4C5E2C41711800B53F9A /* NSObject-SafeExpectations */; };
0CCD4C622C41712800B53F9A /* wpxmlrpc in Frameworks */ = {isa = PBXBuildFile; productRef = 0CCD4C612C41712800B53F9A /* wpxmlrpc */; };
0CE4E8252DC027AC00056DD9 /* RemoteSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE4E8242DC027AC00056DD9 /* RemoteSubscriber.swift */; };
0CED1FE82B617CF300E6DD52 /* AtomicSiteServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CED1FE72B617CF300E6DD52 /* AtomicSiteServiceRemote.swift */; };
0CED1FEB2B617D7D00E6DD52 /* AtomicLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CED1FEA2B617D7D00E6DD52 /* AtomicLogs.swift */; };
1769DEAA24729AFF00F42EFC /* HomepageSettingsServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */; };
Expand Down Expand Up @@ -800,6 +802,7 @@
0C3A2A412A2E7BA500FD91D6 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
0C6183C62C420A3700289E73 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackAIServiceRemote.swift; sourceTree = "<group>"; };
0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "site-subscribers-response.json"; sourceTree = "<group>"; };
0C938A052C416789009BA7B2 /* Secret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secret.swift; sourceTree = "<group>"; };
0C938A072C4167BB009BA7B2 /* NSString+XMLExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+XMLExtensions.h"; sourceTree = "<group>"; };
0C938A082C4167BB009BA7B2 /* NSString+XMLExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+XMLExtensions.m"; sourceTree = "<group>"; };
Expand All @@ -824,6 +827,7 @@
0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blaze-campaigns-search.json"; sourceTree = "<group>"; };
0CB190642A2A7569004D3E80 /* BlazeCampaignsSearchResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsSearchResponse.swift; sourceTree = "<group>"; };
0CCD4C5B2C41700B00B53F9A /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = "<group>"; };
0CE4E8242DC027AC00056DD9 /* RemoteSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteSubscriber.swift; sourceTree = "<group>"; };
0CED1FE72B617CF300E6DD52 /* AtomicSiteServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicSiteServiceRemote.swift; sourceTree = "<group>"; };
0CED1FEA2B617D7D00E6DD52 /* AtomicLogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicLogs.swift; sourceTree = "<group>"; };
1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageSettingsServiceRemote.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1866,6 +1870,7 @@
4A68E3DC294070A7004AC3DC /* RemoteReaderSite.swift */,
4A68E3E0294076C1004AC3DC /* RemoteReaderSiteInfo.swift */,
9F3E0B9A208732B2009CB5BA /* RemoteReaderSiteInfoSubscription.swift */,
0CE4E8242DC027AC00056DD9 /* RemoteSubscriber.swift */,
4A68E3DE29407100004AC3DC /* RemoteReaderTopic.swift */,
74E2295D1F1E777B0085F7F2 /* RemoteSharingButton.swift */,
7430C9C81F192F260051B8E6 /* RemoteSourcePostAttribution.h */,
Expand Down Expand Up @@ -2541,6 +2546,7 @@
9AEAA772215E71C000876E62 /* site-quick-start-success.json */,
74D67F0C1F15C2D70010C5ED /* site-roles-auth-failure.json */,
74D67F0D1F15C2D70010C5ED /* site-roles-bad-json-failure.json */,
0C8069A62DC03E85008DFC2F /* site-subscribers-response.json */,
74D67F0E1F15C2D70010C5ED /* site-roles-success.json */,
D8DB404121EF22B500B8238E /* site-segments-multiple.json */,
D813437721F6D7DC0060D99A /* site-segments-single.json */,
Expand Down Expand Up @@ -3030,6 +3036,7 @@
C738CAF528622953001BE107 /* qrlogin-validate-expired-401.json in Resources */,
937250EC267A15060086075F /* stats-referrer-mark-as-spam.json in Resources */,
F3FF8A23279C954100E5C90F /* site-email-followers-get-success.json in Resources */,
0C8069A72DC03E85008DFC2F /* site-subscribers-response.json in Resources */,
465F889A263B09BF00F4C950 /* wp-block-editor-v1-settings-success-ThemeJSON.json in Resources */,
BA9A7F7F24C6895600925E81 /* plugin-directory-jetpack-beta.json in Resources */,
E6B0461425E5B6F500DF6F4F /* sites-invites-links-generate.json in Resources */,
Expand Down Expand Up @@ -3491,6 +3498,7 @@
3FD634F32BC3AD6200CEDF5E /* Result+Callback.swift in Sources */,
B5A4822E20AC6C1A009D95F6 /* WPKitLogging.m in Sources */,
3FE2E97C2BC3A332002CA2E1 /* WordPressComRestApi.swift in Sources */,
0CE4E8252DC027AC00056DD9 /* RemoteSubscriber.swift in Sources */,
FE6C673C2BB739950083ECAB /* NSAttributedString+extensions.swift in Sources */,
7430C9A61F1927180051B8E6 /* ReaderSiteServiceRemote.m in Sources */,
FEE4EF57272FDD4B003CDA3C /* RemoteCommentV2.swift in Sources */,
Expand Down