Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HACK week] Webhooks #14027

Draft
wants to merge 19 commits into
base: trunk
Choose a base branch
from
Draft
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
28 changes: 28 additions & 0 deletions Networking/Networking.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -512,8 +512,15 @@
57E8FED3246616AC0057CD68 /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E8FED2246616AC0057CD68 /* Result+Extensions.swift */; };
621D043B29C9D4280040EC08 /* product-variation-alternative-types.json in Resources */ = {isa = PBXBuildFile; fileRef = 621D043A29C9D4280040EC08 /* product-variation-alternative-types.json */; };
6647C0161DAC6AB6570C53A7 /* Pods_Networking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */; };
6800B44C2CA3BDE900A64B4F /* WebhookListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6800B44B2CA3BDE900A64B4F /* WebhookListMapper.swift */; };
680910712CA279340057B02A /* Webhook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680910702CA279340057B02A /* Webhook.swift */; };
680910732CA27B900057B02A /* WebhookMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680910722CA27B900057B02A /* WebhookMapper.swift */; };
680910752CA27DC50057B02A /* WebhooksRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680910742CA27DC50057B02A /* WebhooksRemote.swift */; };
6812FC012A6B27E100D7C625 /* InAppPurchasesTransactionMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6812FC002A6B27E100D7C625 /* InAppPurchasesTransactionMapperTests.swift */; };
68255E442C60C6AA00090EBD /* products-load-all-for-eligibility-criteria.json in Resources */ = {isa = PBXBuildFile; fileRef = 68255E432C60C6AA00090EBD /* products-load-all-for-eligibility-criteria.json */; };
682820512CA4F5C600295107 /* WebhooksRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682820502CA4F5C600295107 /* WebhooksRemoteTests.swift */; };
682820532CA4F98F00295107 /* webhooks-single.json in Resources */ = {isa = PBXBuildFile; fileRef = 682820522CA4F98F00295107 /* webhooks-single.json */; };
682820552CA4FEB400295107 /* webhooks-multiple.json in Resources */ = {isa = PBXBuildFile; fileRef = 682820542CA4FEB400295107 /* webhooks-multiple.json */; };
6846B0152A619A5C008EB143 /* InAppPurchasesTransactionMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6846B0142A619A5C008EB143 /* InAppPurchasesTransactionMapper.swift */; };
6846B01B2A61B590008EB143 /* iap-transaction-handled.json in Resources */ = {isa = PBXBuildFile; fileRef = 6846B01A2A61B590008EB143 /* iap-transaction-handled.json */; };
684AB6D82AC2E93100106D7C /* order-with-special-character-currency.json in Resources */ = {isa = PBXBuildFile; fileRef = 684AB6D72AC2E93100106D7C /* order-with-special-character-currency.json */; };
Expand Down Expand Up @@ -1661,8 +1668,15 @@
6132DCC72AA9C070E2033628 /* Pods_NetworkingWatchOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NetworkingWatchOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
61A45743E761BA40FF08B27D /* Pods-NetworkingWatchOS.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkingWatchOS.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-NetworkingWatchOS/Pods-NetworkingWatchOS.release-alpha.xcconfig"; sourceTree = "<group>"; };
621D043A29C9D4280040EC08 /* product-variation-alternative-types.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-variation-alternative-types.json"; sourceTree = "<group>"; };
6800B44B2CA3BDE900A64B4F /* WebhookListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebhookListMapper.swift; sourceTree = "<group>"; };
680910702CA279340057B02A /* Webhook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Webhook.swift; sourceTree = "<group>"; };
680910722CA27B900057B02A /* WebhookMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebhookMapper.swift; sourceTree = "<group>"; };
680910742CA27DC50057B02A /* WebhooksRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebhooksRemote.swift; sourceTree = "<group>"; };
6812FC002A6B27E100D7C625 /* InAppPurchasesTransactionMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesTransactionMapperTests.swift; sourceTree = "<group>"; };
68255E432C60C6AA00090EBD /* products-load-all-for-eligibility-criteria.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "products-load-all-for-eligibility-criteria.json"; sourceTree = "<group>"; };
682820502CA4F5C600295107 /* WebhooksRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebhooksRemoteTests.swift; sourceTree = "<group>"; };
682820522CA4F98F00295107 /* webhooks-single.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "webhooks-single.json"; sourceTree = "<group>"; };
682820542CA4FEB400295107 /* webhooks-multiple.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "webhooks-multiple.json"; sourceTree = "<group>"; };
6846B0142A619A5C008EB143 /* InAppPurchasesTransactionMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesTransactionMapper.swift; sourceTree = "<group>"; };
6846B01A2A61B590008EB143 /* iap-transaction-handled.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "iap-transaction-handled.json"; sourceTree = "<group>"; };
684AB6D72AC2E93100106D7C /* order-with-special-character-currency.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "order-with-special-character-currency.json"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2698,6 +2712,7 @@
DE34051E28BDFB0B00CF0D97 /* JetpackConnectionRemoteTests.swift */,
68BD37B228D9B8BD00C2A517 /* CustomerRemoteTests.swift */,
68F48B0E28E3BB850045C15B /* WCAnalyticsCustomerRemoteTests.swift */,
682820502CA4F5C600295107 /* WebhooksRemoteTests.swift */,
0239306A291A96F800B2632F /* DomainRemoteTests.swift */,
02616F8B292132800095BC00 /* SiteRemoteTests.swift */,
02EF166D292F0C5800D90AD6 /* PaymentRemoteTests.swift */,
Expand Down Expand Up @@ -2857,6 +2872,7 @@
029BA4EF255D7282006171FD /* ShippingLabelRemote.swift */,
311D412F2783C0E200052F64 /* StripeRemote.swift */,
D8EDFE1D25EE87F1003D2213 /* WCPayRemote.swift */,
680910742CA27DC50057B02A /* WebhooksRemote.swift */,
FE28F6E5268429B6004465C7 /* UserRemote.swift */,
077F39D526A58E4500ABEADC /* SystemStatusRemote.swift */,
AEF94584272974F2001DCCFB /* TelemetryRemote.swift */,
Expand Down Expand Up @@ -2999,6 +3015,7 @@
0359EA0C27AAC5F80048DE2D /* WCPayChargeStatus.swift */,
3105470B262E27F000C5C02B /* WCPayPaymentIntentStatusEnum.swift */,
0359EA0E27AAC6410048DE2D /* WCPayPaymentMethodDetails.swift */,
680910702CA279340057B02A /* Webhook.swift */,
209AD3C22AC196E300825D76 /* WooPaymentsDepositsOverview.swift */,
E1BAB2C62913FB5800C3982B /* WordPressApiError.swift */,
DE2E8E9C29530EEF002E4B14 /* WordPressSite.swift */,
Expand Down Expand Up @@ -3422,6 +3439,8 @@
31B8D6B526583970008E3DB2 /* wcpay-account-implicitly-not-eligible.json */,
31799AFB2705189200D78179 /* wcpay-location.json */,
31799AFD270518AD00D78179 /* wcpay-location-error.json */,
682820522CA4F98F00295107 /* webhooks-single.json */,
682820542CA4FEB400295107 /* webhooks-multiple.json */,
02A26F1A2744F5FC008E4EDB /* wp-site-settings.json */,
DEC51AEC2768A0AD009F3DF4 /* systemStatusWithPluginsOnly.json */,
077F39D726A58EB600ABEADC /* systemStatus.json */,
Expand Down Expand Up @@ -3572,6 +3591,8 @@
FE28F6E326842848004465C7 /* UserMapper.swift */,
077F39D326A58DE700ABEADC /* SystemStatusMapper.swift */,
DEC51AE827687AAF009F3DF4 /* SystemPluginMapper.swift */,
680910722CA27B900057B02A /* WebhookMapper.swift */,
6800B44B2CA3BDE900A64B4F /* WebhookListMapper.swift */,
02C11275274285FF00F4F0B4 /* WooCommerceAvailabilityMapper.swift */,
02BE0A7A274B695F001176D2 /* WordPressMediaMapper.swift */,
02C112772742862600F4F0B4 /* WordPressSiteSettingsMapper.swift */,
Expand Down Expand Up @@ -4335,6 +4356,7 @@
45AF57A924AB42CD0088E2F7 /* product-tags-extra.json in Resources */,
74AB5B4F21AF3F0E00859C12 /* site-api-no-woo.json in Resources */,
DE66C559297799D000DAA978 /* add-on-groups-without-data.json in Resources */,
682820552CA4FEB400295107 /* webhooks-multiple.json in Resources */,
265BCA02243056E3004E53EE /* categories-all.json in Resources */,
D8FBFF2422D52815006E3336 /* order-stats-v4-daily.json in Resources */,
EEE846A22A54745F005239B6 /* identify-language-success.json in Resources */,
Expand Down Expand Up @@ -4520,6 +4542,7 @@
DE2004702BFB535500660A72 /* product-report.json in Resources */,
EE57C13E297FBEE200BC31E7 /* product-tags-created-without-data.json in Resources */,
CE21FB242C2C16DA00303832 /* google-ads-reports-programs.json in Resources */,
682820532CA4F98F00295107 /* webhooks-single.json in Resources */,
EEA658462966C67C00112DF0 /* products-ids-only-without-data.json in Resources */,
DE2004762C001F7500660A72 /* variation-report.json in Resources */,
DE2095C127966EC800171F1C /* coupon-reports.json in Resources */,
Expand Down Expand Up @@ -4872,6 +4895,7 @@
09EA564B27C75FCE00407D40 /* ProductVariationsBulkUpdateMapper.swift in Sources */,
CE227093228DD44C00C0626C /* ProductStatus.swift in Sources */,
451A97E9260B657D0059D135 /* ShippingLabelPredefinedOption.swift in Sources */,
6800B44C2CA3BDE900A64B4F /* WebhookListMapper.swift in Sources */,
263659DC2A264A3E00607A0D /* IPLocationRemote.swift in Sources */,
CEB9BF432BB199600007978A /* ProductBundleStatsMapper.swift in Sources */,
02C2548425635BD000A04423 /* ShippingLabelPaperSize.swift in Sources */,
Expand All @@ -4886,6 +4910,7 @@
DEFBA74E29485A7600C35BA9 /* RESTRequest.swift in Sources */,
456930A9264EB576009ED69D /* ShippingLabelCarriersAndRates.swift in Sources */,
741B950120EBC8A700DD6E2D /* OrderCouponLine.swift in Sources */,
680910732CA27B900057B02A /* WebhookMapper.swift in Sources */,
020D07BA23D8542000FD9580 /* UploadableMedia.swift in Sources */,
DEC2961C26BBE764005A056B /* ShippingLabelCustomsForm.swift in Sources */,
31D27C8B26028D96002EDB1D /* SitePlugin.swift in Sources */,
Expand Down Expand Up @@ -5097,6 +5122,7 @@
4568E2222459ADC60007E478 /* SitePostsRemote.swift in Sources */,
02C11276274285FF00F4F0B4 /* WooCommerceAvailabilityMapper.swift in Sources */,
DEB387762C2A9A140025256E /* GoogleAdsConnectionMapper.swift in Sources */,
680910752CA27DC50057B02A /* WebhooksRemote.swift in Sources */,
CC0786C5267BAF0F00BA9AC1 /* ShippingLabelStatusMapper.swift in Sources */,
4515280D257A7EEC0076B03C /* ProductAttributeListMapper.swift in Sources */,
93D8BBFD226BBEE800AD2EB3 /* AccountSettingsMapper.swift in Sources */,
Expand Down Expand Up @@ -5242,6 +5268,7 @@
45150A9E26836A57006922EA /* CountryListMapper.swift in Sources */,
DE34051928BDEE6A00CF0D97 /* JetpackConnectionURLMapper.swift in Sources */,
CE6BFEE82236D133005C79FB /* ProductDimensions.swift in Sources */,
680910712CA279340057B02A /* Webhook.swift in Sources */,
077F39D426A58DE700ABEADC /* SystemStatusMapper.swift in Sources */,
45152811257A81730076B03C /* ProductAttributeMapper.swift in Sources */,
DEB387732C2A8F9A0025256E /* GoogleAdsConnection.swift in Sources */,
Expand Down Expand Up @@ -5450,6 +5477,7 @@
74002D6C2118B88200A63C19 /* SiteStatsRemoteTests.swift in Sources */,
0212683524C046CB00F8A892 /* MockNetwork+Path.swift in Sources */,
EE1217DC2AFE04A500E6CAB1 /* ProductVariationEncoderTests.swift in Sources */,
682820512CA4F5C600295107 /* WebhooksRemoteTests.swift in Sources */,
68BD37B328D9B8BD00C2A517 /* CustomerRemoteTests.swift in Sources */,
B554FA932180C17200C54DFF /* NoteHashListMapperTests.swift in Sources */,
EEC312C52AFE01BC004369F7 /* ProductEncoderTests.swift in Sources */,
Expand Down
33 changes: 33 additions & 0 deletions Networking/Networking/Mapper/WebhookListMapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

struct WebhookListMapper: Mapper {
/// Identifier associated to the webhooks that will be parsed from a given site
/// We're injecting this field via `JSONDecoder.userInfo` because the remote endpoints for webhook don't return the SiteID
///
let siteID: Int64

/// Attempts to convert a dictionary into a `[Webhook]` object
///
func map(response: Data) throws -> [Webhook] {
let decoder = JSONDecoder()
decoder.userInfo = [
.siteID: siteID
]

if hasDataEnvelope(in: response) {
let decodedResponse = try decoder.decode(WebhookListEnvelope.self, from: response).webhooks
return decodedResponse
} else {
let decodedResponse = try decoder.decode([Webhook].self, from: response)
return decodedResponse
}
}
}

struct WebhookListEnvelope: Decodable {
let webhooks: [Webhook]

private enum CodingKeys: String, CodingKey {
case webhooks = "data"
}
}
33 changes: 33 additions & 0 deletions Networking/Networking/Mapper/WebhookMapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

struct WebhookMapper: Mapper {
/// Identifier associated to the webhooks that will be parsed from a given site
/// We're injecting this field via `JSONDecoder.userInfo` because the remote endpoints for webhook don't return the SiteID
///
let siteID: Int64

/// Attempts to convert a dictionary into a `Webhook` object
///
func map(response: Data) throws -> Webhook {
let decoder = JSONDecoder()
decoder.userInfo = [
.siteID: siteID
]

if hasDataEnvelope(in: response) {
let decodedResponse = try decoder.decode(WebhookEnvelope.self, from: response).webhook
return decodedResponse
} else {
let decodedResponse = try decoder.decode(Webhook.self, from: response)
return decodedResponse
}
}
}

struct WebhookEnvelope: Decodable {
let webhook: Webhook

private enum CodingKeys: String, CodingKey {
case webhook = "data"
}
}
62 changes: 62 additions & 0 deletions Networking/Networking/Model/Webhook.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Foundation
import Codegen

/// Represents a Webhook entity:
/// https://woocommerce.github.io/woocommerce-rest-api-docs/#webhooks
///
public struct Webhook: Codable, Equatable {
/// The siteID for the webhook
public let siteID: Int64

public let name: String?
public let status: String
public let topic: String
public let deliveryURL: URL

/// Webhook struct initializer
///
public init(siteID: Int64,
name: String?,
status: String,
topic: String,
deliveryURL: URL) {
self.siteID = siteID
self.name = name
self.status = status
self.topic = topic
self.deliveryURL = deliveryURL
}

/// Public initializer for the Webhook
///
public init(from decoder: any Decoder) throws {
guard let siteID = decoder.userInfo[.siteID] as? Int64 else {
throw WebhookDecodingError.missingSiteID
}
let container = try decoder.container(keyedBy: CodingKeys.self)

let name = try container.decodeIfPresent(String.self, forKey: .name)
let status = try container.decode(String.self, forKey: .status)
let topic = try container.decode(String.self, forKey: .topic)
let deliveryURL = try container.decode(URL.self, forKey: .deliveryURL)

self.init(siteID: siteID,
name: name,
status: status,
topic: topic,
deliveryURL: deliveryURL)
}
}

extension Webhook {
enum CodingKeys: String, CodingKey {
case name
case status
case topic
case deliveryURL = "delivery_url"
}

enum WebhookDecodingError: Error {
case missingSiteID
}
}
31 changes: 31 additions & 0 deletions Networking/Networking/Remote/WebhooksRemote.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Foundation

public class WebhooksRemote: Remote {
public func listAllWebhooks(for siteID: Int64) async throws -> [Webhook] {
let request = JetpackRequest(wooApiVersion: .mark3,
method: .get,
siteID: siteID,
path: "webhooks",
availableAsRESTRequest: true)
let mapper = WebhookListMapper(siteID: siteID)

return try await enqueue(request, mapper: mapper)
}

public func createWebhook(for siteID: Int64, topic: String, url: URL) async throws -> Webhook {
let parameters = [
"topic": "\(topic)",
"delivery_url": "\(url.absoluteString)"
]

let request = JetpackRequest(wooApiVersion: .mark3,
method: .post,
siteID: siteID,
path: "webhooks",
parameters: parameters,
availableAsRESTRequest: true)
let mapper = WebhookMapper(siteID: siteID)

return try await enqueue(request, mapper: mapper)
}
}
Loading