diff --git a/Package.swift b/Package.swift index ef33ea7..cdbc96a 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( .library(name: "Orders", targets: ["Orders"]), ], dependencies: [ - .package(url: "https://github.com/vapor/vapor.git", from: "4.107.0"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.108.0"), .package(url: "https://github.com/vapor/fluent.git", from: "4.12.0"), .package(url: "https://github.com/vapor/apns.git", from: "4.2.0"), .package(url: "https://github.com/vapor-community/Zip.git", from: "2.2.4"), diff --git a/Sources/Orders/Middleware/OrdersService+AsyncModelMiddleware.swift b/Sources/Orders/Middleware/OrdersService+AsyncModelMiddleware.swift new file mode 100644 index 0000000..0a845b1 --- /dev/null +++ b/Sources/Orders/Middleware/OrdersService+AsyncModelMiddleware.swift @@ -0,0 +1,42 @@ +import FluentKit +import Foundation + +extension OrdersService: AsyncModelMiddleware { + public func create(model: OD, on db: any Database, next: any AnyAsyncModelResponder) async throws { + let order = Order( + typeIdentifier: OD.typeIdentifier, + authenticationToken: Data([UInt8].random(count: 12)).base64EncodedString() + ) + try await order.save(on: db) + model._$order.id = try order.requireID() + try await next.create(model, on: db) + } + + public func update(model: OD, on db: any Database, next: any AnyAsyncModelResponder) async throws { + let order = try await model._$order.get(on: db) + order.updatedAt = Date.now + try await order.save(on: db) + try await next.update(model, on: db) + try await self.sendPushNotifications(for: model, on: db) + } +} + +extension OrdersServiceCustom: AsyncModelMiddleware { + public func create(model: OD, on db: any Database, next: any AnyAsyncModelResponder) async throws { + let order = O( + typeIdentifier: OD.typeIdentifier, + authenticationToken: Data([UInt8].random(count: 12)).base64EncodedString() + ) + try await order.save(on: db) + model._$order.id = try order.requireID() + try await next.create(model, on: db) + } + + public func update(model: OD, on db: any Database, next: any AnyAsyncModelResponder) async throws { + let order = try await model._$order.get(on: db) + order.updatedAt = Date.now + try await order.save(on: db) + try await next.update(model, on: db) + try await self.sendPushNotifications(for: model, on: db) + } +} diff --git a/Sources/Orders/Models/OrderModel.swift b/Sources/Orders/Models/OrderModel.swift index c247966..803cb94 100644 --- a/Sources/Orders/Models/OrderModel.swift +++ b/Sources/Orders/Models/OrderModel.swift @@ -16,6 +16,12 @@ public protocol OrderModel: Model where IDValue == UUID { /// The authentication token supplied to your web service. var authenticationToken: String { get set } + + /// The designated initializer. + /// - Parameters: + /// - typeIdentifier: The order type identifier that’s registered with Apple. + /// - authenticationToken: The authentication token to use with the web service in the `webServiceURL` key. + init(typeIdentifier: String, authenticationToken: String) } extension OrderModel { diff --git a/Sources/Orders/Orders.docc/GettingStarted.md b/Sources/Orders/Orders.docc/GettingStarted.md index 7e1c08c..e015b74 100644 --- a/Sources/Orders/Orders.docc/GettingStarted.md +++ b/Sources/Orders/Orders.docc/GettingStarted.md @@ -199,49 +199,18 @@ OrdersService.register(migrations: app.migrations) ### Order Data Model Middleware -You'll want to create a model middleware to handle the creation and update of the order data model. -This middleware could be responsible for creating and linking an ``Order`` to the order data model, depending on your requirements. -When your order data changes, it should also update the ``Order/updatedAt`` field of the ``Order`` and send a push notification to all devices registered to that order. +This framework provides a model middleware to handle the creation and update of the order data model. -```swift -import Vapor -import Fluent -import Orders - -struct OrderDataMiddleware: AsyncModelMiddleware { - private unowned let service: OrdersService - - init(service: OrdersService) { - self.service = service - } - - // Create the `Order` and add it to the `OrderData` automatically at creation - func create(model: OrderData, on db: Database, next: AnyAsyncModelResponder) async throws { - let order = Order( - typeIdentifier: Environment.get("ORDER_TYPE_IDENTIFIER")!, - authenticationToken: Data([UInt8].random(count: 12)).base64EncodedString()) - try await order.save(on: db) - model.$order.id = try order.requireID() - try await next.create(model, on: db) - } - - func update(model: OrderData, on db: Database, next: AnyAsyncModelResponder) async throws { - let order = try await model.$order.get(on: db) - order.updatedAt = Date() - try await order.save(on: db) - try await next.update(model, on: db) - try await service.sendPushNotifications(for: model, on: db) - } -} -``` +When you create an ``OrderDataModel`` object, it will automatically create an ``OrderModel`` object with a random auth token and the correct type identifier and link it to the order data model. +When you update an order data model, it will update the ``OrderModel`` object and send a push notification to all devices registered to that order. -You could register it in the `configure.swift` file. +You can register it like so (either with an ``OrdersService`` or an ``OrdersServiceCustom``): ```swift -app.databases.middleware.use(OrderDataMiddleware(service: ordersService), on: .psql) +app.databases.middleware.use(ordersService, on: .psql) ``` -> Important: Whenever your order data changes, you must update the ``Order/updatedAt`` time of the linked ``Order`` so that Wallet knows to retrieve a new order. +> Note: If you don't like the default implementation of the model middleware, it is highly recommended that you create your own. But remember: whenever your order data changes, you must update the ``Order/updatedAt`` time of the linked ``Order`` so that Wallet knows to retrieve a new order. ### Generate the Order Content diff --git a/Sources/Passes/Middleware/PassesService+AsyncModelMiddleware.swift b/Sources/Passes/Middleware/PassesService+AsyncModelMiddleware.swift new file mode 100644 index 0000000..23c37e3 --- /dev/null +++ b/Sources/Passes/Middleware/PassesService+AsyncModelMiddleware.swift @@ -0,0 +1,42 @@ +import FluentKit +import Foundation + +extension PassesService: AsyncModelMiddleware { + public func create(model: PD, on db: any Database, next: any AnyAsyncModelResponder) async throws { + let pass = Pass( + typeIdentifier: PD.typeIdentifier, + authenticationToken: Data([UInt8].random(count: 12)).base64EncodedString() + ) + try await pass.save(on: db) + model._$pass.id = try pass.requireID() + try await next.create(model, on: db) + } + + public func update(model: PD, on db: any Database, next: any AnyAsyncModelResponder) async throws { + let pass = try await model._$pass.get(on: db) + pass.updatedAt = Date.now + try await pass.save(on: db) + try await next.update(model, on: db) + try await self.sendPushNotifications(for: model, on: db) + } +} + +extension PassesServiceCustom: AsyncModelMiddleware { + public func create(model: PD, on db: any Database, next: any AnyAsyncModelResponder) async throws { + let pass = P( + typeIdentifier: PD.typeIdentifier, + authenticationToken: Data([UInt8].random(count: 12)).base64EncodedString() + ) + try await pass.save(on: db) + model._$pass.id = try pass.requireID() + try await next.create(model, on: db) + } + + public func update(model: PD, on db: any Database, next: any AnyAsyncModelResponder) async throws { + let pass = try await model._$pass.get(on: db) + pass.updatedAt = Date.now + try await pass.save(on: db) + try await next.update(model, on: db) + try await self.sendPushNotifications(for: model, on: db) + } +} diff --git a/Sources/Passes/Passes.docc/GettingStarted.md b/Sources/Passes/Passes.docc/GettingStarted.md index 2b55c69..a788f88 100644 --- a/Sources/Passes/Passes.docc/GettingStarted.md +++ b/Sources/Passes/Passes.docc/GettingStarted.md @@ -224,49 +224,18 @@ PassesService.register(migrations: app.migrations) ### Pass Data Model Middleware -You'll want to create a model middleware to handle the creation and update of the pass data model. -This middleware could be responsible for creating and linking a ``Pass`` to the pass data model, depending on your requirements. -When your pass data changes, it should also update the ``Pass/updatedAt`` field of the ``Pass`` and send a push notification to all devices registered to that pass. +This framework provides a model middleware to handle the creation and update of the pass data model. -```swift -import Vapor -import Fluent -import Passes - -struct PassDataMiddleware: AsyncModelMiddleware { - private unowned let service: PassesService - - init(service: PassesService) { - self.service = service - } - - // Create the `Pass` and add it to the `PassData` automatically at creation - func create(model: PassData, on db: Database, next: AnyAsyncModelResponder) async throws { - let pass = Pass( - typeIdentifier: Environment.get("PASS_TYPE_IDENTIFIER")!, - authenticationToken: Data([UInt8].random(count: 12)).base64EncodedString()) - try await pass.save(on: db) - model.$pass.id = try pass.requireID() - try await next.create(model, on: db) - } - - func update(model: PassData, on db: Database, next: AnyAsyncModelResponder) async throws { - let pass = try await model.$pass.get(on: db) - pass.updatedAt = Date() - try await pass.save(on: db) - try await next.update(model, on: db) - try await service.sendPushNotifications(for: model, on: db) - } -} -``` +When you create a ``PassDataModel`` object, it will automatically create a ``PassModel`` object with a random auth token and the correct type identifier and link it to the pass data model. +When you update a pass data model, it will update the ``PassModel`` object and send a push notification to all devices registered to that pass. -You could register it in the `configure.swift` file. +You can register it like so (either with a ``PassesService`` or a ``PassesServiceCustom``): ```swift -app.databases.middleware.use(PassDataMiddleware(service: passesService), on: .psql) +app.databases.middleware.use(passesService, on: .psql) ``` -> Important: Whenever your pass data changes, you must update the ``Pass/updatedAt`` time of the linked ``Pass`` so that Wallet knows to retrieve a new pass. +> Note: If you don't like the default implementation of the model middleware, it is highly recommended that you create your own. But remember: whenever your pass data changes, you must update the ``Pass/updatedAt`` time of the linked ``Pass`` so that Wallet knows to retrieve a new pass. ### Generate the Pass Content diff --git a/Tests/OrdersTests/OrdersTests.swift b/Tests/OrdersTests/OrdersTests.swift index 0d9f462..2fb7901 100644 --- a/Tests/OrdersTests/OrdersTests.swift +++ b/Tests/OrdersTests/OrdersTests.swift @@ -384,7 +384,7 @@ struct OrdersTests { ) if !useEncryptedKey { - // Test `OrderDataMiddleware` update method + // Test `AsyncModelMiddleware` update method orderData.title = "Test Order 2" do { try await orderData.update(on: app.db) diff --git a/Tests/OrdersTests/Utils/OrderDataMiddleware.swift b/Tests/OrdersTests/Utils/OrderDataMiddleware.swift deleted file mode 100644 index d82da3f..0000000 --- a/Tests/OrdersTests/Utils/OrderDataMiddleware.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Fluent -import Foundation -import Orders - -struct OrderDataMiddleware: AsyncModelMiddleware { - private unowned let service: OrdersService - - init(service: OrdersService) { - self.service = service - } - - func create(model: OrderData, on db: any Database, next: any AnyAsyncModelResponder) async throws { - let order = Order( - typeIdentifier: OrderData.typeIdentifier, - authenticationToken: Data([UInt8].random(count: 12)).base64EncodedString() - ) - try await order.save(on: db) - model.$order.id = try order.requireID() - try await next.create(model, on: db) - } - - func update(model: OrderData, on db: any Database, next: any AnyAsyncModelResponder) async throws { - let order = try await model.$order.get(on: db) - order.updatedAt = Date() - try await order.save(on: db) - try await next.update(model, on: db) - try await service.sendPushNotifications(for: model, on: db) - } -} diff --git a/Tests/OrdersTests/Utils/withApp.swift b/Tests/OrdersTests/Utils/withApp.swift index 0c8e595..0d726dd 100644 --- a/Tests/OrdersTests/Utils/withApp.swift +++ b/Tests/OrdersTests/Utils/withApp.swift @@ -28,7 +28,7 @@ func withApp( pemPrivateKey: useEncryptedKey ? TestCertificate.encryptedPemPrivateKey : TestCertificate.pemPrivateKey, pemPrivateKeyPassword: useEncryptedKey ? "password" : nil ) - app.databases.middleware.use(OrderDataMiddleware(service: ordersService), on: .sqlite) + app.databases.middleware.use(ordersService, on: .sqlite) Zip.addCustomFileExtension("order") diff --git a/Tests/PassesTests/PassesTests.swift b/Tests/PassesTests/PassesTests.swift index 821b050..3661783 100644 --- a/Tests/PassesTests/PassesTests.swift +++ b/Tests/PassesTests/PassesTests.swift @@ -522,7 +522,7 @@ struct PassesTests { ) if !useEncryptedKey { - // Test `PassDataMiddleware` update method + // Test `AsyncModelMiddleware` update method passData.title = "Test Pass 2" do { try await passData.update(on: app.db) diff --git a/Tests/PassesTests/Utils/PassDataMiddleware.swift b/Tests/PassesTests/Utils/PassDataMiddleware.swift deleted file mode 100644 index 2183ab0..0000000 --- a/Tests/PassesTests/Utils/PassDataMiddleware.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Fluent -import Foundation -import Passes - -struct PassDataMiddleware: AsyncModelMiddleware { - private unowned let service: PassesService - - init(service: PassesService) { - self.service = service - } - - func create(model: PassData, on db: any Database, next: any AnyAsyncModelResponder) async throws { - let pass = Pass( - typeIdentifier: PassData.typeIdentifier, - authenticationToken: Data([UInt8].random(count: 12)).base64EncodedString() - ) - try await pass.save(on: db) - model.$pass.id = try pass.requireID() - try await next.create(model, on: db) - } - - func update(model: PassData, on db: any Database, next: any AnyAsyncModelResponder) async throws { - let pass = try await model.$pass.get(on: db) - pass.updatedAt = Date() - try await pass.save(on: db) - try await next.update(model, on: db) - try await service.sendPushNotifications(for: model, on: db) - } -} diff --git a/Tests/PassesTests/Utils/withApp.swift b/Tests/PassesTests/Utils/withApp.swift index f452baa..84e3363 100644 --- a/Tests/PassesTests/Utils/withApp.swift +++ b/Tests/PassesTests/Utils/withApp.swift @@ -28,7 +28,7 @@ func withApp( pemPrivateKey: useEncryptedKey ? TestCertificate.encryptedPemPrivateKey : TestCertificate.pemPrivateKey, pemPrivateKeyPassword: useEncryptedKey ? "password" : nil ) - app.databases.middleware.use(PassDataMiddleware(service: passesService), on: .sqlite) + app.databases.middleware.use(passesService, on: .sqlite) Zip.addCustomFileExtension("pkpass")