Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
18 changes: 18 additions & 0 deletions Modules/Sources/Fakes/Networking.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,24 @@ extension Networking.Booking {
)
}
}
extension Networking.BookingResource {
/// Returns a "ready to use" type filled with fake values.
///
public static func fake() -> Networking.BookingResource {
.init(
siteID: .fake(),
resourceID: .fake(),
name: .fake(),
quantity: .fake(),
role: .fake(),
email: .fake(),
phoneNumber: .fake(),
imageID: .fake(),
imageURL: .fake(),
description: .fake()
)
}
}
extension Networking.CompositeComponentOptionType {
/// Returns a "ready to use" type filled with fake values.
///
Expand Down
27 changes: 27 additions & 0 deletions Modules/Sources/Networking/Mapper/BookingResourceMapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation

/// Mapper: BookingResource
///
struct BookingResourceMapper: Mapper {
let siteID: Int64

func map(response: Data) throws -> BookingResource {
let decoder = JSONDecoder()
decoder.userInfo = [
.siteID: siteID
]
if hasDataEnvelope(in: response) {
return try decoder.decode(BookingResourceEnvelope.self, from: response).bookingResource
} else {
return try decoder.decode(BookingResource.self, from: response)
}
}
}

private struct BookingResourceEnvelope: Decodable {
let bookingResource: BookingResource

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

public struct BookingResource: Hashable, Decodable, GeneratedFakeable, GeneratedCopiable {
public let siteID: Int64
public let resourceID: Int64
public let name: String
public let quantity: Int64
public let role: String
public let email: String?
public let phoneNumber: String?
public let imageID: Int64
public let imageURL: String?
public let description: String?

public init(siteID: Int64,
resourceID: Int64,
name: String,
quantity: Int64,
role: String,
email: String?,
phoneNumber: String?,
imageID: Int64,
imageURL: String?,
description: String?) {
self.siteID = siteID
self.resourceID = resourceID
self.name = name
self.quantity = quantity
self.role = role
self.email = email
self.phoneNumber = phoneNumber
self.imageID = imageID
self.imageURL = imageURL
self.description = description
}

public init(from decoder: Decoder) throws {
guard let siteID = decoder.userInfo[.siteID] as? Int64 else {
throw BookingResourceDecodingError.missingSiteID
}

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

let resourceID = try container.decode(Int64.self, forKey: .resourceID)
let name = try container.decode(String.self, forKey: .name)
let quantity = try container.decode(Int64.self, forKey: .quantity)
let role = try container.decode(String.self, forKey: .role)
let email = try container.decodeIfPresent(String.self, forKey: .email)
let phoneNumber = try container.decodeIfPresent(String.self, forKey: .phoneNumber)
let imageID = try container.decode(Int64.self, forKey: .imageID)
let imageURL = try container.decodeIfPresent(String.self, forKey: .imageURL)
let description = try container.decodeIfPresent(String.self, forKey: .description)

self.init(siteID: siteID,
resourceID: resourceID,
name: name,
quantity: quantity,
role: role,
email: email,
phoneNumber: phoneNumber,
imageID: imageID,
imageURL: imageURL,
description: description)
}
}

private extension BookingResource {
enum CodingKeys: String, CodingKey {
case resourceID = "id"
case name
case quantity = "qty"
case role
case email
case phoneNumber = "phone_number"
case imageID = "image_id"
case imageURL = "image_url"
case description
}
}

// MARK: - Decoding Errors
//
enum BookingResourceDecodingError: Error {
case missingSiteID
}
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,45 @@ extension Networking.Booking {
}
}

extension Networking.BookingResource {
public func copy(
siteID: CopiableProp<Int64> = .copy,
resourceID: CopiableProp<Int64> = .copy,
name: CopiableProp<String> = .copy,
quantity: CopiableProp<Int64> = .copy,
role: CopiableProp<String> = .copy,
email: NullableCopiableProp<String> = .copy,
phoneNumber: NullableCopiableProp<String> = .copy,
imageID: CopiableProp<Int64> = .copy,
imageURL: NullableCopiableProp<String> = .copy,
description: NullableCopiableProp<String> = .copy
) -> Networking.BookingResource {
let siteID = siteID ?? self.siteID
let resourceID = resourceID ?? self.resourceID
let name = name ?? self.name
let quantity = quantity ?? self.quantity
let role = role ?? self.role
let email = email ?? self.email
let phoneNumber = phoneNumber ?? self.phoneNumber
let imageID = imageID ?? self.imageID
let imageURL = imageURL ?? self.imageURL
let description = description ?? self.description

return Networking.BookingResource(
siteID: siteID,
resourceID: resourceID,
name: name,
quantity: quantity,
role: role,
email: email,
phoneNumber: phoneNumber,
imageID: imageID,
imageURL: imageURL,
description: description
)
}
}

extension Networking.Coupon {
public func copy(
siteID: CopiableProp<Int64> = .copy,
Expand Down
22 changes: 22 additions & 0 deletions Modules/Sources/Networking/Remote/BookingsRemote.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public protocol BookingsRemoteProtocol {

func loadBooking(bookingID: Int64,
siteID: Int64) async throws -> Booking?

func fetchResource(resourceID: Int64,
siteID: Int64) async throws -> BookingResource?
}

/// Booking: Remote Endpoints
Expand Down Expand Up @@ -84,6 +87,24 @@ public final class BookingsRemote: Remote, BookingsRemoteProtocol {

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

public func fetchResource(
resourceID: Int64,
siteID: Int64
) async throws -> BookingResource? {
let path = "\(Path.resources)/\(resourceID)"
let request = JetpackRequest(
wooApiVersion: .wcBookings,
method: .get,
siteID: siteID,
path: path,
availableAsRESTRequest: true
)

let mapper = BookingResourceMapper(siteID: siteID)

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

// MARK: - Constants
Expand All @@ -101,6 +122,7 @@ public extension BookingsRemote {

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

private enum ParameterKey {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation
import CoreData

@objc(BookingResource)
public class BookingResource: NSManagedObject {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation
import CoreData

extension BookingResource {
@NSManaged public var siteID: Int64
@NSManaged public var resourceID: Int64
@NSManaged public var name: String?
@NSManaged public var quantity: Int64
@NSManaged public var role: String?
@NSManaged public var email: String?
@NSManaged public var phoneNumber: String?
@NSManaged public var imageID: Int64
@NSManaged public var imageURL: String?
@NSManaged public var descriptionText: String?

}
2 changes: 2 additions & 0 deletions Modules/Sources/Storage/Model/MIGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This file documents changes in the WCiOS Storage data model. Please explain any
- Added `BookingProductInfo` entity.
- Added `BookingPaymentInfo` entity.
- Added `orderInfo` relationship to `Booking` entity.
- @itsmeichigo 2025-10-16
- Added `BookingResource` entity.

## Model 127 (Release 23.4.0.0)
- @itsmeichigo 2025-09-23
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@
<attribute name="name" attributeType="String" defaultValueString=""/>
<relationship name="orderInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BookingOrderInfo" inverseName="productInfo" inverseEntity="BookingOrderInfo"/>
</entity>
<entity name="BookingResource" representedClassName="BookingResource" syncable="YES">
<attribute name="descriptionText" optional="YES" attributeType="String"/>
<attribute name="email" optional="YES" attributeType="String"/>
<attribute name="imageID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="imageURL" optional="YES" attributeType="String"/>
<attribute name="name" attributeType="String" defaultValueString=""/>
<attribute name="phoneNumber" optional="YES" attributeType="String"/>
<attribute name="quantity" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="resourceID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" attributeType="String" defaultValueString=""/>
<attribute name="siteID" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
</entity>
<entity name="Country" representedClassName="Country" syncable="YES">
<attribute name="code" attributeType="String"/>
<attribute name="name" attributeType="String"/>
Expand Down
6 changes: 6 additions & 0 deletions Modules/Sources/Storage/Tools/StorageType+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -966,4 +966,10 @@ public extension StorageType {
let objects = allObjects(ofType: Booking.self, matching: predicate, sortedBy: [descriptor])
return objects.isEmpty ? nil : objects
}

/// Retrieves the store booking resource
func loadBookingResource(siteID: Int64, resourceID: Int64) -> BookingResource? {
let predicate = \BookingResource.resourceID == resourceID && \BookingResource.siteID == siteID
return firstObject(ofType: BookingResource.self, matching: predicate)
}
}
8 changes: 8 additions & 0 deletions Modules/Sources/Yosemite/Actions/BookingAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,12 @@ public enum BookingAction: Action {
startDateAfter: String? = nil,
order: BookingsRemote.Order = .descending,
onCompletion: (Result<[Booking], Error>) -> Void)

/// Fetches a booking resource by resource ID.
///
/// - Parameter onCompletion: called when fetch completes, returns an error or the booking resource.
///
case fetchResource(siteID: Int64,
resourceID: Int64,
onCompletion: (Result<BookingResource, Error>) -> Void)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Foundation
import Storage

// MARK: - Storage.BookingResource: ReadOnlyConvertible
//
extension Storage.BookingResource: ReadOnlyConvertible {

/// Updates the Storage.BookingResource with the a ReadOnly.
///
public func update(with resource: Yosemite.BookingResource) {
siteID = resource.siteID
resourceID = resource.resourceID
name = resource.name
quantity = resource.quantity
role = resource.role
email = resource.email
phoneNumber = resource.phoneNumber
imageID = resource.imageID
imageURL = resource.imageURL
descriptionText = resource.description
}

/// Returns a ReadOnly version of the receiver.
///
public func toReadOnly() -> Yosemite.BookingResource {
BookingResource(siteID: siteID,
resourceID: resourceID,
name: name ?? "",
quantity: quantity,
role: role ?? "",
email: email,
phoneNumber: phoneNumber,
imageID: imageID,
imageURL: imageURL,
description: descriptionText)
}
}

extension Yosemite.BookingResource: ReadOnlyType {
/// Indicates if the receiver is a representation of a specified Storage.Entity instance.
///
public func isReadOnlyRepresentation(of storageEntity: Any) -> Bool {
guard let storageResource = storageEntity as? Storage.BookingResource else {
return false
}

return siteID == Int(storageResource.siteID) && resourceID == Int(storageResource.resourceID)
}
}
1 change: 1 addition & 0 deletions Modules/Sources/Yosemite/Model/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public typealias BookingOrderInfo = Networking.BookingOrderInfo
public typealias BookingCustomerInfo = Networking.BookingCustomerInfo
public typealias BookingPaymentInfo = Networking.BookingPaymentInfo
public typealias BookingProductInfo = Networking.BookingProductInfo
public typealias BookingResource = Networking.BookingResource
public typealias CreateBlazeCampaign = Networking.CreateBlazeCampaign
public typealias FallibleCancelable = Hardware.FallibleCancelable
public typealias CommentStatus = Networking.CommentStatus
Expand Down
Loading