Skip to content

Commit 261ddc5

Browse files
authored
Bookings: Update Networking layer to support fetching Bookings (#16162)
2 parents a7f8e59 + 0e16d10 commit 261ddc5

File tree

13 files changed

+589
-184
lines changed

13 files changed

+589
-184
lines changed

Modules/Sources/Fakes/Networking.generated.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,31 @@ extension Networking.BlazeTargetTopic {
317317
)
318318
}
319319
}
320+
extension Networking.Booking {
321+
/// Returns a "ready to use" type filled with fake values.
322+
///
323+
public static func fake() -> Networking.Booking {
324+
.init(
325+
siteID: .fake(),
326+
bookingID: .fake(),
327+
allDay: .fake(),
328+
cost: .fake(),
329+
customerID: .fake(),
330+
dateCreated: .fake(),
331+
dateModified: .fake(),
332+
endDate: .fake(),
333+
googleCalendarEventID: .fake(),
334+
orderID: .fake(),
335+
orderItemID: .fake(),
336+
parentID: .fake(),
337+
productID: .fake(),
338+
resourceID: .fake(),
339+
startDate: .fake(),
340+
statusKey: .fake(),
341+
localTimezone: .fake()
342+
)
343+
}
344+
}
320345
extension Networking.CompositeComponentOptionType {
321346
/// Returns a "ready to use" type filled with fake values.
322347
///
@@ -1775,9 +1800,9 @@ extension Networking.Site {
17751800
wasEcommerceTrial: .fake(),
17761801
hasSSOEnabled: .fake(),
17771802
applicationPasswordAvailable: .fake(),
1778-
isGarden: false,
1779-
gardenName: nil,
1780-
gardenPartner: nil
1803+
isGarden: .fake(),
1804+
gardenName: .fake(),
1805+
gardenPartner: .fake()
17811806
)
17821807
}
17831808
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// periphery:ignore:all
2+
import Codegen
3+
import Foundation
4+
5+
/// Represents a Booking Entity.
6+
///
7+
public struct Booking: Codable, GeneratedCopiable, Equatable, GeneratedFakeable {
8+
public let siteID: Int64
9+
public let bookingID: Int64
10+
public let allDay: Bool
11+
public let cost: String
12+
public let customerID: Int64
13+
public let dateCreated: Date
14+
public let dateModified: Date
15+
public let endDate: Date
16+
public let googleCalendarEventID: String?
17+
public let orderID: Int64
18+
public let orderItemID: Int64
19+
public let parentID: Int64
20+
public let productID: Int64
21+
public let resourceID: Int64
22+
public let startDate: Date
23+
public let statusKey: String
24+
public let localTimezone: String
25+
26+
/// Computed Properties
27+
///
28+
public var bookingStatus: BookingStatus {
29+
return BookingStatus(rawValue: statusKey) ?? .unknown
30+
}
31+
32+
/// Booking struct initializer.
33+
///
34+
public init(siteID: Int64,
35+
bookingID: Int64,
36+
allDay: Bool,
37+
cost: String,
38+
customerID: Int64,
39+
dateCreated: Date,
40+
dateModified: Date,
41+
endDate: Date,
42+
googleCalendarEventID: String?,
43+
orderID: Int64,
44+
orderItemID: Int64,
45+
parentID: Int64,
46+
productID: Int64,
47+
resourceID: Int64,
48+
startDate: Date,
49+
statusKey: String,
50+
localTimezone: String) {
51+
self.siteID = siteID
52+
self.bookingID = bookingID
53+
self.allDay = allDay
54+
self.cost = cost
55+
self.customerID = customerID
56+
self.dateCreated = dateCreated
57+
self.dateModified = dateModified
58+
self.endDate = endDate
59+
self.googleCalendarEventID = googleCalendarEventID
60+
self.orderID = orderID
61+
self.orderItemID = orderItemID
62+
self.parentID = parentID
63+
self.productID = productID
64+
self.resourceID = resourceID
65+
self.startDate = startDate
66+
self.statusKey = statusKey
67+
self.localTimezone = localTimezone
68+
}
69+
70+
/// The public initializer for Booking.
71+
///
72+
public init(from decoder: Decoder) throws {
73+
guard let siteID = decoder.userInfo[.siteID] as? Int64 else {
74+
throw BookingDecodingError.missingSiteID
75+
}
76+
77+
let container = try decoder.container(keyedBy: CodingKeys.self)
78+
79+
let bookingID = try container.decode(Int64.self, forKey: .bookingID)
80+
let allDay = try container.decode(Bool.self, forKey: .allDay)
81+
82+
// Cost may come as string or number
83+
let cost = container.failsafeDecodeIfPresent(targetType: String.self,
84+
forKey: .cost,
85+
alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? ""
86+
87+
let customerID = try container.decode(Int64.self, forKey: .customerID)
88+
let dateCreated = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .dateCreated))
89+
let dateModified = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .dateModified))
90+
let endDate = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .endDate))
91+
let googleCalendarEventID = try container.decodeIfPresent(String.self, forKey: .googleCalendarEventID)
92+
let orderID = try container.decode(Int64.self, forKey: .orderID)
93+
let orderItemID = try container.decode(Int64.self, forKey: .orderItemID)
94+
let parentID = try container.decode(Int64.self, forKey: .parentID)
95+
let productID = try container.decode(Int64.self, forKey: .productID)
96+
let resourceID = try container.decode(Int64.self, forKey: .resourceID)
97+
let startDate = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .startDate))
98+
let statusKey = try container.decode(String.self, forKey: .statusKey)
99+
let localTimezone = try container.decode(String.self, forKey: .localTimezone)
100+
101+
self.init(siteID: siteID,
102+
bookingID: bookingID,
103+
allDay: allDay,
104+
cost: cost,
105+
customerID: customerID,
106+
dateCreated: dateCreated,
107+
dateModified: dateModified,
108+
endDate: endDate,
109+
googleCalendarEventID: googleCalendarEventID,
110+
orderID: orderID,
111+
orderItemID: orderItemID,
112+
parentID: parentID,
113+
productID: productID,
114+
resourceID: resourceID,
115+
startDate: startDate,
116+
statusKey: statusKey,
117+
localTimezone: localTimezone)
118+
}
119+
120+
public func encode(to encoder: Encoder) throws {
121+
var container = encoder.container(keyedBy: CodingKeys.self)
122+
123+
try container.encode(bookingID, forKey: .bookingID)
124+
try container.encode(allDay, forKey: .allDay)
125+
try container.encode(cost, forKey: .cost)
126+
try container.encode(customerID, forKey: .customerID)
127+
try container.encode(dateCreated, forKey: .dateCreated)
128+
try container.encode(dateModified, forKey: .dateModified)
129+
try container.encode(endDate, forKey: .endDate)
130+
try container.encode(googleCalendarEventID, forKey: .googleCalendarEventID)
131+
try container.encode(orderID, forKey: .orderID)
132+
try container.encode(orderItemID, forKey: .orderItemID)
133+
try container.encode(parentID, forKey: .parentID)
134+
try container.encode(productID, forKey: .productID)
135+
try container.encode(resourceID, forKey: .resourceID)
136+
try container.encode(startDate, forKey: .startDate)
137+
try container.encode(statusKey, forKey: .statusKey)
138+
try container.encode(localTimezone, forKey: .localTimezone)
139+
}
140+
}
141+
142+
/// Defines all of the Booking CodingKeys
143+
///
144+
private extension Booking {
145+
146+
enum CodingKeys: String, CodingKey {
147+
case bookingID = "id"
148+
case allDay = "all_day"
149+
case cost
150+
case customerID = "customer_id"
151+
case dateCreated = "date_created"
152+
case dateModified = "date_modified"
153+
case endDate = "end"
154+
case googleCalendarEventID = "google_calendar_event_id"
155+
case orderID = "order_id"
156+
case orderItemID = "order_item_id"
157+
case parentID = "parent_id"
158+
case personCounts = "person_counts"
159+
case productID = "product_id"
160+
case resourceID = "resource_id"
161+
case startDate = "start"
162+
case statusKey = "status"
163+
case localTimezone = "local_timezone"
164+
}
165+
}
166+
167+
// MARK: - Decoding Errors
168+
//
169+
enum BookingDecodingError: Error {
170+
case missingSiteID
171+
}
172+
173+
// MARK: - Supporting Types
174+
//
175+
176+
/// Represents a Booking Status.
177+
///
178+
public enum BookingStatus: String, CaseIterable {
179+
case complete
180+
case paid
181+
case unpaid
182+
case cancelled
183+
case pendingConfirmation = "pending-confirmation"
184+
case confirmed
185+
case inCart = "in-cart"
186+
case unknown
187+
}

Modules/Sources/Networking/Model/Copiable/Models+Copiable.generated.swift

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,66 @@ extension Networking.BlazeTargetTopic {
428428
}
429429
}
430430

431+
extension Networking.Booking {
432+
public func copy(
433+
siteID: CopiableProp<Int64> = .copy,
434+
bookingID: CopiableProp<Int64> = .copy,
435+
allDay: CopiableProp<Bool> = .copy,
436+
cost: CopiableProp<String> = .copy,
437+
customerID: CopiableProp<Int64> = .copy,
438+
dateCreated: CopiableProp<Date> = .copy,
439+
dateModified: CopiableProp<Date> = .copy,
440+
endDate: CopiableProp<Date> = .copy,
441+
googleCalendarEventID: NullableCopiableProp<String> = .copy,
442+
orderID: CopiableProp<Int64> = .copy,
443+
orderItemID: CopiableProp<Int64> = .copy,
444+
parentID: CopiableProp<Int64> = .copy,
445+
productID: CopiableProp<Int64> = .copy,
446+
resourceID: CopiableProp<Int64> = .copy,
447+
startDate: CopiableProp<Date> = .copy,
448+
statusKey: CopiableProp<String> = .copy,
449+
localTimezone: CopiableProp<String> = .copy
450+
) -> Networking.Booking {
451+
let siteID = siteID ?? self.siteID
452+
let bookingID = bookingID ?? self.bookingID
453+
let allDay = allDay ?? self.allDay
454+
let cost = cost ?? self.cost
455+
let customerID = customerID ?? self.customerID
456+
let dateCreated = dateCreated ?? self.dateCreated
457+
let dateModified = dateModified ?? self.dateModified
458+
let endDate = endDate ?? self.endDate
459+
let googleCalendarEventID = googleCalendarEventID ?? self.googleCalendarEventID
460+
let orderID = orderID ?? self.orderID
461+
let orderItemID = orderItemID ?? self.orderItemID
462+
let parentID = parentID ?? self.parentID
463+
let productID = productID ?? self.productID
464+
let resourceID = resourceID ?? self.resourceID
465+
let startDate = startDate ?? self.startDate
466+
let statusKey = statusKey ?? self.statusKey
467+
let localTimezone = localTimezone ?? self.localTimezone
468+
469+
return Networking.Booking(
470+
siteID: siteID,
471+
bookingID: bookingID,
472+
allDay: allDay,
473+
cost: cost,
474+
customerID: customerID,
475+
dateCreated: dateCreated,
476+
dateModified: dateModified,
477+
endDate: endDate,
478+
googleCalendarEventID: googleCalendarEventID,
479+
orderID: orderID,
480+
orderItemID: orderItemID,
481+
parentID: parentID,
482+
productID: productID,
483+
resourceID: resourceID,
484+
startDate: startDate,
485+
statusKey: statusKey,
486+
localTimezone: localTimezone
487+
)
488+
}
489+
}
490+
431491
extension Networking.Coupon {
432492
public func copy(
433493
siteID: CopiableProp<Int64> = .copy,
@@ -2749,8 +2809,8 @@ extension Networking.Site {
27492809
hasSSOEnabled: CopiableProp<Bool> = .copy,
27502810
applicationPasswordAvailable: CopiableProp<Bool> = .copy,
27512811
isGarden: CopiableProp<Bool> = .copy,
2752-
gardenName: CopiableProp<String?> = .copy,
2753-
gardenPartner: CopiableProp<String?> = .copy
2812+
gardenName: NullableCopiableProp<String> = .copy,
2813+
gardenPartner: NullableCopiableProp<String> = .copy
27542814
) -> Networking.Site {
27552815
let siteID = siteID ?? self.siteID
27562816
let name = name ?? self.name
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// periphery:ignore:all
2+
import Foundation
3+
4+
/// Protocol for `BookingsRemote` mainly used for mocking.
5+
///
6+
/// The required methods are intentionally incomplete. Feel free to add the other ones.
7+
///
8+
public protocol BookingsRemoteProtocol {
9+
func loadAllBookings(for siteID: Int64,
10+
pageNumber: Int,
11+
pageSize: Int) async throws -> [Booking]
12+
}
13+
14+
/// Booking: Remote Endpoints
15+
///
16+
public final class BookingsRemote: Remote, BookingsRemoteProtocol {
17+
18+
// MARK: - Bookings
19+
20+
/// Retrieves all of the `Bookings` available.
21+
///
22+
/// - Parameters:
23+
/// - siteID: Site for which we'll fetch remote bookings.
24+
/// - pageNumber: Number of page that should be retrieved.
25+
/// - pageSize: Number of bookings to be retrieved per page.
26+
///
27+
public func loadAllBookings(for siteID: Int64,
28+
pageNumber: Int = Default.pageNumber,
29+
pageSize: Int = Default.pageSize) async throws -> [Booking] {
30+
let parameters = [
31+
ParameterKey.page: String(pageNumber),
32+
ParameterKey.perPage: String(pageSize)
33+
]
34+
35+
let path = Path.bookings
36+
let request = JetpackRequest(wooApiVersion: .wcBookings, method: .get, siteID: siteID, path: path, parameters: parameters, availableAsRESTRequest: true)
37+
let mapper = ListMapper<Booking>(siteID: siteID)
38+
39+
return try await enqueue(request, mapper: mapper)
40+
}
41+
}
42+
43+
// MARK: - Constants
44+
//
45+
public extension BookingsRemote {
46+
enum Default {
47+
public static let pageSize: Int = 25
48+
public static let pageNumber: Int = Remote.Default.firstPageNumber
49+
}
50+
51+
private enum Path {
52+
static let bookings = "bookings"
53+
}
54+
55+
private enum ParameterKey {
56+
static let page: String = "page"
57+
static let perPage: String = "per_page"
58+
}
59+
}

Modules/Sources/NetworkingCore/Settings/WooAPIVersion.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public enum WooAPIVersion: String {
5050
///
5151
case wooShipping = "wcshipping/v1"
5252

53+
/// WooCommerce Bookings Plugin V1.
54+
///
55+
case wcBookings = "wc-bookings/v2"
56+
5357
/// Returns the path for the current API Version
5458
///
5559
var path: String {

0 commit comments

Comments
 (0)