Skip to content
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
31 changes: 28 additions & 3 deletions Modules/Sources/Fakes/Networking.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,31 @@ extension Networking.BlazeTargetTopic {
)
}
}
extension Networking.Booking {
/// Returns a "ready to use" type filled with fake values.
///
public static func fake() -> Networking.Booking {
.init(
siteID: .fake(),
bookingID: .fake(),
allDay: .fake(),
cost: .fake(),
customerID: .fake(),
dateCreated: .fake(),
dateModified: .fake(),
endDate: .fake(),
googleCalendarEventID: .fake(),
orderID: .fake(),
orderItemID: .fake(),
parentID: .fake(),
productID: .fake(),
resourceID: .fake(),
startDate: .fake(),
statusKey: .fake(),
localTimezone: .fake()
)
}
}
extension Networking.CompositeComponentOptionType {
/// Returns a "ready to use" type filled with fake values.
///
Expand Down Expand Up @@ -1775,9 +1800,9 @@ extension Networking.Site {
wasEcommerceTrial: .fake(),
hasSSOEnabled: .fake(),
applicationPasswordAvailable: .fake(),
isGarden: false,
gardenName: nil,
gardenPartner: nil
isGarden: .fake(),
gardenName: .fake(),
gardenPartner: .fake()
)
}
}
Expand Down
187 changes: 187 additions & 0 deletions Modules/Sources/Networking/Model/Bookings/Booking.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// periphery:ignore:all
import Codegen
import Foundation

/// Represents a Booking Entity.
///
public struct Booking: Codable, GeneratedCopiable, Equatable, GeneratedFakeable {
public let siteID: Int64
public let bookingID: Int64
public let allDay: Bool
public let cost: String
public let customerID: Int64
public let dateCreated: Date
public let dateModified: Date
public let endDate: Date
public let googleCalendarEventID: String?
public let orderID: Int64
public let orderItemID: Int64
public let parentID: Int64
public let productID: Int64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the person_counts skipped on purpose? Not sure though what it could be used for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I skipped it since I'm unsure about the structure (sometimes it's an array sometimes dictionary). Since we don't have a clear idea how to use it/what for, I figure we can add it later when needed. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm good

public let resourceID: Int64
public let startDate: Date
public let statusKey: String
public let localTimezone: String

/// Computed Properties
///
public var bookingStatus: BookingStatus {
return BookingStatus(rawValue: statusKey) ?? .unknown
}

/// Booking struct initializer.
///
public init(siteID: Int64,
bookingID: Int64,
allDay: Bool,
cost: String,
customerID: Int64,
dateCreated: Date,
dateModified: Date,
endDate: Date,
googleCalendarEventID: String?,
orderID: Int64,
orderItemID: Int64,
parentID: Int64,
productID: Int64,
resourceID: Int64,
startDate: Date,
statusKey: String,
localTimezone: String) {
self.siteID = siteID
self.bookingID = bookingID
self.allDay = allDay
self.cost = cost
self.customerID = customerID
self.dateCreated = dateCreated
self.dateModified = dateModified
self.endDate = endDate
self.googleCalendarEventID = googleCalendarEventID
self.orderID = orderID
self.orderItemID = orderItemID
self.parentID = parentID
self.productID = productID
self.resourceID = resourceID
self.startDate = startDate
self.statusKey = statusKey
self.localTimezone = localTimezone
}

/// The public initializer for Booking.
///
public init(from decoder: Decoder) throws {
guard let siteID = decoder.userInfo[.siteID] as? Int64 else {
throw BookingDecodingError.missingSiteID
}

let container = try decoder.container(keyedBy: CodingKeys.self)

let bookingID = try container.decode(Int64.self, forKey: .bookingID)
let allDay = try container.decode(Bool.self, forKey: .allDay)

// Cost may come as string or number
let cost = container.failsafeDecodeIfPresent(targetType: String.self,
forKey: .cost,
alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? ""

let customerID = try container.decode(Int64.self, forKey: .customerID)
let dateCreated = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .dateCreated))
let dateModified = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .dateModified))
let endDate = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .endDate))
let googleCalendarEventID = try container.decodeIfPresent(String.self, forKey: .googleCalendarEventID)
let orderID = try container.decode(Int64.self, forKey: .orderID)
let orderItemID = try container.decode(Int64.self, forKey: .orderItemID)
let parentID = try container.decode(Int64.self, forKey: .parentID)
let productID = try container.decode(Int64.self, forKey: .productID)
let resourceID = try container.decode(Int64.self, forKey: .resourceID)
let startDate = Date(timeIntervalSince1970: try container.decode(Double.self, forKey: .startDate))
let statusKey = try container.decode(String.self, forKey: .statusKey)
let localTimezone = try container.decode(String.self, forKey: .localTimezone)

self.init(siteID: siteID,
bookingID: bookingID,
allDay: allDay,
cost: cost,
customerID: customerID,
dateCreated: dateCreated,
dateModified: dateModified,
endDate: endDate,
googleCalendarEventID: googleCalendarEventID,
orderID: orderID,
orderItemID: orderItemID,
parentID: parentID,
productID: productID,
resourceID: resourceID,
startDate: startDate,
statusKey: statusKey,
localTimezone: localTimezone)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(bookingID, forKey: .bookingID)
try container.encode(allDay, forKey: .allDay)
try container.encode(cost, forKey: .cost)
try container.encode(customerID, forKey: .customerID)
try container.encode(dateCreated, forKey: .dateCreated)
try container.encode(dateModified, forKey: .dateModified)
try container.encode(endDate, forKey: .endDate)
try container.encode(googleCalendarEventID, forKey: .googleCalendarEventID)
try container.encode(orderID, forKey: .orderID)
try container.encode(orderItemID, forKey: .orderItemID)
try container.encode(parentID, forKey: .parentID)
try container.encode(productID, forKey: .productID)
try container.encode(resourceID, forKey: .resourceID)
try container.encode(startDate, forKey: .startDate)
try container.encode(statusKey, forKey: .statusKey)
try container.encode(localTimezone, forKey: .localTimezone)
}
}

/// Defines all of the Booking CodingKeys
///
private extension Booking {

enum CodingKeys: String, CodingKey {
case bookingID = "id"
case allDay = "all_day"
case cost
case customerID = "customer_id"
case dateCreated = "date_created"
case dateModified = "date_modified"
case endDate = "end"
case googleCalendarEventID = "google_calendar_event_id"
case orderID = "order_id"
case orderItemID = "order_item_id"
case parentID = "parent_id"
case personCounts = "person_counts"
case productID = "product_id"
case resourceID = "resource_id"
case startDate = "start"
case statusKey = "status"
case localTimezone = "local_timezone"
}
}

// MARK: - Decoding Errors
//
enum BookingDecodingError: Error {
case missingSiteID
}

// MARK: - Supporting Types
//

/// Represents a Booking Status.
///
public enum BookingStatus: String, CaseIterable {
case complete
case paid
case unpaid
case cancelled
case pendingConfirmation = "pending-confirmation"
case confirmed
case inCart = "in-cart"
case unknown
}
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,66 @@ extension Networking.BlazeTargetTopic {
}
}

extension Networking.Booking {
public func copy(
siteID: CopiableProp<Int64> = .copy,
bookingID: CopiableProp<Int64> = .copy,
allDay: CopiableProp<Bool> = .copy,
cost: CopiableProp<String> = .copy,
customerID: CopiableProp<Int64> = .copy,
dateCreated: CopiableProp<Date> = .copy,
dateModified: CopiableProp<Date> = .copy,
endDate: CopiableProp<Date> = .copy,
googleCalendarEventID: NullableCopiableProp<String> = .copy,
orderID: CopiableProp<Int64> = .copy,
orderItemID: CopiableProp<Int64> = .copy,
parentID: CopiableProp<Int64> = .copy,
productID: CopiableProp<Int64> = .copy,
resourceID: CopiableProp<Int64> = .copy,
startDate: CopiableProp<Date> = .copy,
statusKey: CopiableProp<String> = .copy,
localTimezone: CopiableProp<String> = .copy
) -> Networking.Booking {
let siteID = siteID ?? self.siteID
let bookingID = bookingID ?? self.bookingID
let allDay = allDay ?? self.allDay
let cost = cost ?? self.cost
let customerID = customerID ?? self.customerID
let dateCreated = dateCreated ?? self.dateCreated
let dateModified = dateModified ?? self.dateModified
let endDate = endDate ?? self.endDate
let googleCalendarEventID = googleCalendarEventID ?? self.googleCalendarEventID
let orderID = orderID ?? self.orderID
let orderItemID = orderItemID ?? self.orderItemID
let parentID = parentID ?? self.parentID
let productID = productID ?? self.productID
let resourceID = resourceID ?? self.resourceID
let startDate = startDate ?? self.startDate
let statusKey = statusKey ?? self.statusKey
let localTimezone = localTimezone ?? self.localTimezone

return Networking.Booking(
siteID: siteID,
bookingID: bookingID,
allDay: allDay,
cost: cost,
customerID: customerID,
dateCreated: dateCreated,
dateModified: dateModified,
endDate: endDate,
googleCalendarEventID: googleCalendarEventID,
orderID: orderID,
orderItemID: orderItemID,
parentID: parentID,
productID: productID,
resourceID: resourceID,
startDate: startDate,
statusKey: statusKey,
localTimezone: localTimezone
)
}
}

extension Networking.Coupon {
public func copy(
siteID: CopiableProp<Int64> = .copy,
Expand Down Expand Up @@ -2749,8 +2809,8 @@ extension Networking.Site {
hasSSOEnabled: CopiableProp<Bool> = .copy,
applicationPasswordAvailable: CopiableProp<Bool> = .copy,
isGarden: CopiableProp<Bool> = .copy,
gardenName: CopiableProp<String?> = .copy,
gardenPartner: CopiableProp<String?> = .copy
gardenName: NullableCopiableProp<String> = .copy,
gardenPartner: NullableCopiableProp<String> = .copy
) -> Networking.Site {
let siteID = siteID ?? self.siteID
let name = name ?? self.name
Expand Down
59 changes: 59 additions & 0 deletions Modules/Sources/Networking/Remote/BookingsRemote.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// periphery:ignore:all
import Foundation

/// Protocol for `BookingsRemote` mainly used for mocking.
///
/// The required methods are intentionally incomplete. Feel free to add the other ones.
///
public protocol BookingsRemoteProtocol {
func loadAllBookings(for siteID: Int64,
pageNumber: Int,
pageSize: Int) async throws -> [Booking]
}

/// Booking: Remote Endpoints
///
public final class BookingsRemote: Remote, BookingsRemoteProtocol {

// MARK: - Bookings

/// Retrieves all of the `Bookings` available.
///
/// - Parameters:
/// - siteID: Site for which we'll fetch remote bookings.
/// - pageNumber: Number of page that should be retrieved.
/// - pageSize: Number of bookings to be retrieved per page.
///
public func loadAllBookings(for siteID: Int64,
pageNumber: Int = Default.pageNumber,
pageSize: Int = Default.pageSize) async throws -> [Booking] {
let parameters = [
ParameterKey.page: String(pageNumber),
ParameterKey.perPage: String(pageSize)
]

let path = Path.bookings
let request = JetpackRequest(wooApiVersion: .wcBookings, method: .get, siteID: siteID, path: path, parameters: parameters, availableAsRESTRequest: true)
let mapper = ListMapper<Booking>(siteID: siteID)

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

// MARK: - Constants
//
public extension BookingsRemote {
enum Default {
public static let pageSize: Int = 25
public static let pageNumber: Int = Remote.Default.firstPageNumber
}

private enum Path {
static let bookings = "bookings"
}

private enum ParameterKey {
static let page: String = "page"
static let perPage: String = "per_page"
}
}
4 changes: 4 additions & 0 deletions Modules/Sources/NetworkingCore/Settings/WooAPIVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public enum WooAPIVersion: String {
///
case wooShipping = "wcshipping/v1"

/// WooCommerce Bookings Plugin V1.
///
case wcBookings = "wc-bookings/v2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it v2 or should be v1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will have to use v2 for the MVP. There's a long thread about testing Bookings for the MVP in p1758101141913349-slack-C03L1NF1EA3, but tl;dr you can follow the steps:

  • Create a garden site and upload the latest Bookings plugin from trunk
  • Install Code Snippets plugin and add this script define( 'WC_BOOKINGS_NEXT_ENABLED', true );

This would allow you to test the v2 API. However, I was not able to overwrite the Bookings plugin in the garden sites I created so Jorge invited me to his site instead. Let me know if you need an invitation too.


/// Returns the path for the current API Version
///
var path: String {
Expand Down
Loading