Skip to content

Commit

Permalink
Provide default model middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
fpseverino committed Dec 16, 2024
1 parent ce8ada5 commit 8752829
Show file tree
Hide file tree
Showing 12 changed files with 107 additions and 137 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
42 changes: 42 additions & 0 deletions Sources/Orders/Middleware/OrdersService+AsyncModelMiddleware.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
6 changes: 6 additions & 0 deletions Sources/Orders/Models/OrderModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
43 changes: 6 additions & 37 deletions Sources/Orders/Orders.docc/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,49 +199,18 @@ OrdersService<OrderData>.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<OrderData>

init(service: OrdersService<OrderData>) {
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

Expand Down
42 changes: 42 additions & 0 deletions Sources/Passes/Middleware/PassesService+AsyncModelMiddleware.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
43 changes: 6 additions & 37 deletions Sources/Passes/Passes.docc/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,49 +224,18 @@ PassesService<PassData>.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<PassData>

init(service: PassesService<PassData>) {
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

Expand Down
2 changes: 1 addition & 1 deletion Tests/OrdersTests/OrdersTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 0 additions & 29 deletions Tests/OrdersTests/Utils/OrderDataMiddleware.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Tests/OrdersTests/Utils/withApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
2 changes: 1 addition & 1 deletion Tests/PassesTests/PassesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 0 additions & 29 deletions Tests/PassesTests/Utils/PassDataMiddleware.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Tests/PassesTests/Utils/withApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down

0 comments on commit 8752829

Please sign in to comment.