Skip to content

Commit

Permalink
Improve DooC
Browse files Browse the repository at this point in the history
  • Loading branch information
fpseverino committed Jun 24, 2024
1 parent e982c4f commit 33b80ac
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 26 deletions.
4 changes: 4 additions & 0 deletions .spi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version: 1
builder:
configs:
- documentation_targets: [Passes]
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let package = Package(
.package(url: "https://github.com/vapor/vapor.git", from: "4.102.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.11.0"),
.package(url: "https://github.com/vapor/apns.git", from: "4.1.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0")
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
],
targets: [
.target(
Expand All @@ -22,7 +22,7 @@ let package = Package(
.product(name: "Fluent", package: "fluent"),
.product(name: "Vapor", package: "vapor"),
.product(name: "VaporAPNS", package: "apns"),
.product(name: "Logging", package: "swift-log")
.product(name: "Logging", package: "swift-log"),
],
swiftSettings: swiftSettings
),
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ app.apns.containers.use(
eventLoopGroupProvider: .shared(app.eventLoopGroup),
responseDecoder: JSONDecoder(),
requestEncoder: JSONEncoder(),
as: .init(string: "passkit"),
as: .init(string: "passes"),
isDefault: false
)
```
Expand Down
4 changes: 4 additions & 0 deletions Sources/Passes/Models/ConcreteModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import Vapor
import Fluent

/// The `Model` that stores PassKit devices.
final public class PKDevice: PassKitDevice, @unchecked Sendable {
public static let schema = "devices"

Expand Down Expand Up @@ -64,6 +65,7 @@ extension PKDevice: AsyncMigration {
}
}

/// The `Model` that stores PassKit passes.
open class PKPass: PassKitPass, @unchecked Sendable {
public static let schema = "passes"

Expand Down Expand Up @@ -97,6 +99,7 @@ extension PKPass: AsyncMigration {
}
}

/// The `Model` that stores PassKit error logs.
final public class PKErrorLog: PassKitErrorLog, @unchecked Sendable {
public static let schema = "errors"

Expand Down Expand Up @@ -130,6 +133,7 @@ extension PKErrorLog: AsyncMigration {
}
}

/// The `Model` that stores PassKit registrations.
final public class PKRegistration: PassKitRegistration, @unchecked Sendable {
public typealias PassType = PKPass
public typealias DeviceType = PKDevice
Expand Down
2 changes: 1 addition & 1 deletion Sources/Passes/Models/PassKitPass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import Vapor
import Fluent

/// Represents the `Model` that stores PassKit passes. Uses a UUID so people can't easily guess your pass IDs
/// Represents the `Model` that stores PassKit passes. Uses a UUID so people can't easily guess pass IDs
public protocol PassKitPass: Model where IDValue == UUID {
/// The pass type identifier.
var passTypeIdentifier: String { get set }
Expand Down
1 change: 1 addition & 0 deletions Sources/Passes/Models/PassKitPassData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import Vapor
import Fluent

/// Represents the `Model` that stores custom app data associated to PassKit passes.
public protocol PassKitPassData: Model {
associatedtype PassType: PassKitPass

Expand Down
76 changes: 54 additions & 22 deletions Sources/Passes/Passes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import VaporAPNS
import Fluent
import NIOSSL

/// The main class that handles PassKit passes.
public final class Passes: Sendable {
private let kit: PassesCustom<PKPass, PKDevice, PKRegistration, PKErrorLog>

Expand All @@ -47,35 +48,66 @@ public final class Passes: Sendable {
kit.registerRoutes(authorizationCode: authorizationCode)
}

/// Registers routes to send push notifications to updated passes.
///
/// - Parameter middleware: The `Middleware` which will control authentication for the routes.
public func registerPushRoutes(middleware: any Middleware) throws {
try kit.registerPushRoutes(middleware: middleware)
}

/// Generates the pass content bundle for a given pass.
///
/// - Parameters:
/// - pass: The pass to generate the content for.
/// - db: The `Database` to use.
/// - Returns: The generated pass content.
public func generatePassContent(for pass: PKPass, on db: any Database) async throws -> Data {
try await kit.generatePassContent(for: pass, on: db)
}

/// Adds the migrations for PassKit passes models.
///
/// - Parameter migrations: The `Migrations` object to add the migrations to.
public static func register(migrations: Migrations) {
migrations.add(PKPass())
migrations.add(PKDevice())
migrations.add(PKRegistration())
migrations.add(PKErrorLog())
}

public static func sendPushNotificationsForPass(id: UUID, of type: String, on db: any Database, app: Application) async throws {
try await PassesCustom<PKPass, PKDevice, PKRegistration, PKErrorLog>.sendPushNotificationsForPass(id: id, of: type, on: db, app: app)
/// Sends push notifications for a given pass.
///
/// - Parameters:
/// - id: The `UUID` of the pass to send the notifications for.
/// - passTypeIdentifier: The type identifier of the pass.
/// - db: The `Database` to use.
/// - app: The `Application` to use.
public static func sendPushNotificationsForPass(id: UUID, of passTypeIdentifier: String, on db: any Database, app: Application) async throws {
try await PassesCustom<PKPass, PKDevice, PKRegistration, PKErrorLog>.sendPushNotificationsForPass(id: id, of: passTypeIdentifier, on: db, app: app)
}

/// Sends push notifications for a given pass.
///
/// - Parameters:
/// - pass: The pass to send the notifications for.
/// - db: The `Database` to use.
/// - app: The `Application` to use.
public static func sendPushNotifications(for pass: PKPass, on db: any Database, app: Application) async throws {
try await PassesCustom<PKPass, PKDevice, PKRegistration, PKErrorLog>.sendPushNotifications(for: pass, on: db, app: app)
}

/// Sends push notifications for a given pass.
///
/// - Parameters:
/// - pass: The pass (as the `ParentProperty`) to send the notifications for.
/// - db: The `Database` to use.
/// - app: The `Application` to use.
public static func sendPushNotifications(for pass: ParentProperty<PKRegistration, PKPass>, on db: any Database, app: Application) async throws {
try await PassesCustom<PKPass, PKDevice, PKRegistration, PKErrorLog>.sendPushNotifications(for: pass, on: db, app: app)
}
}

/// Class to handle Passes.
/// Class to handle `Passes`.
///
/// The generics should be passed in this order:
/// - Pass Type
Expand All @@ -102,7 +134,7 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog
///
/// - Parameter authorizationCode: The `authenticationToken` which you are going to use in the `pass.json` file.
public func registerRoutes(authorizationCode: String? = nil) {
v1.value.get("devices", ":deviceLibraryIdentifier", "registrations", ":type", use: { try await self.passesForDevice(req: $0) })
v1.value.get("devices", ":deviceLibraryIdentifier", "registrations", ":passTypeIdentifier", use: { try await self.passesForDevice(req: $0) })
v1.value.post("log", use: { try await self.logError(req: $0) })

guard let code = authorizationCode ?? Environment.get("PASS_KIT_AUTHORIZATION") else {
Expand All @@ -111,9 +143,9 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog

let v1auth = v1.value.grouped(ApplePassMiddleware(authorizationCode: code))

v1auth.post("devices", ":deviceLibraryIdentifier", "registrations", ":type", ":passSerial", use: { try await self.registerDevice(req: $0) })
v1auth.get("passes", ":type", ":passSerial", use: { try await self.latestVersionOfPass(req: $0) })
v1auth.delete("devices", ":deviceLibraryIdentifier", "registrations", ":type", ":passSerial", use: { try await self.unregisterDevice(req: $0) })
v1auth.post("devices", ":deviceLibraryIdentifier", "registrations", ":passTypeIdentifier", ":passSerial", use: { try await self.registerDevice(req: $0) })
v1auth.get("passes", ":passTypeIdentifier", ":passSerial", use: { try await self.latestVersionOfPass(req: $0) })
v1auth.delete("devices", ":deviceLibraryIdentifier", "registrations", ":passTypeIdentifier", ":passSerial", use: { try await self.unregisterDevice(req: $0) })
}

/// Registers routes to send push notifications for updated passes
Expand Down Expand Up @@ -172,8 +204,8 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog

let pushAuth = v1.value.grouped(middleware)

pushAuth.post("push", ":type", ":passSerial", use: { try await self.pushUpdatesForPass(req: $0) })
pushAuth.get("push", ":type", ":passSerial", use: { try await self.tokensForPassUpdate(req: $0) })
pushAuth.post("push", ":passTypeIdentifier", ":passSerial", use: { try await self.pushUpdatesForPass(req: $0) })
pushAuth.get("push", ":passTypeIdentifier", ":passSerial", use: { try await self.tokensForPassUpdate(req: $0) })
}

// MARK: - API Routes
Expand All @@ -192,7 +224,7 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog
throw Abort(.badRequest)
}

let passTypeIdentifier = req.parameters.get("type")!
let passTypeIdentifier = req.parameters.get("passTypeIdentifier")!
let deviceLibraryIdentifier = req.parameters.get("deviceLibraryIdentifier")!

guard let pass = try await P.query(on: req.db)
Expand Down Expand Up @@ -220,10 +252,10 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog
func passesForDevice(req: Request) async throws -> PassesForDeviceDTO {
logger?.debug("Called passesForDevice")

let type = req.parameters.get("type")!
let passTypeIdentifier = req.parameters.get("passTypeIdentifier")!
let deviceLibraryIdentifier = req.parameters.get("deviceLibraryIdentifier")!

var query = R.for(deviceLibraryIdentifier: deviceLibraryIdentifier, passTypeIdentifier: type, on: req.db)
var query = R.for(deviceLibraryIdentifier: deviceLibraryIdentifier, passTypeIdentifier: passTypeIdentifier, on: req.db)

if let since: TimeInterval = req.query["passesUpdatedSince"] {
let when = Date(timeIntervalSince1970: since)
Expand Down Expand Up @@ -263,7 +295,7 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog
ifModifiedSince = ims
}

guard let passTypeIdentifier = req.parameters.get("type"),
guard let passTypeIdentifier = req.parameters.get("passTypeIdentifier"),
let id = req.parameters.get("passSerial", as: UUID.self) else {
throw Abort(.badRequest)
}
Expand Down Expand Up @@ -294,15 +326,15 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog
func unregisterDevice(req: Request) async throws -> HTTPStatus {
logger?.debug("Called unregisterDevice")

let type = req.parameters.get("type")!
let passTypeIdentifier = req.parameters.get("passTypeIdentifier")!

guard let passId = req.parameters.get("passSerial", as: UUID.self) else {
throw Abort(.badRequest)
}

let deviceLibraryIdentifier = req.parameters.get("deviceLibraryIdentifier")!

guard let r = try await R.for(deviceLibraryIdentifier: deviceLibraryIdentifier, passTypeIdentifier: type, on: req.db)
guard let r = try await R.for(deviceLibraryIdentifier: deviceLibraryIdentifier, passTypeIdentifier: passTypeIdentifier, on: req.db)
.filter(P.self, \._$id == passId)
.first()
else {
Expand Down Expand Up @@ -339,9 +371,9 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog
throw Abort(.badRequest)
}

let type = req.parameters.get("type")!
let passTypeIdentifier = req.parameters.get("passTypeIdentifier")!

try await Self.sendPushNotificationsForPass(id: id, of: type, on: req.db, app: req.application)
try await Self.sendPushNotificationsForPass(id: id, of: passTypeIdentifier, on: req.db, app: req.application)
return .noContent
}

Expand All @@ -352,9 +384,9 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog
throw Abort(.badRequest)
}

let type = req.parameters.get("type")!
let passTypeIdentifier = req.parameters.get("passTypeIdentifier")!

let registrations = try await Self.registrationsForPass(id: id, of: type, on: req.db)
let registrations = try await Self.registrationsForPass(id: id, of: passTypeIdentifier, on: req.db)
return registrations.map { $0.device.pushToken }
}

Expand All @@ -376,12 +408,12 @@ public final class PassesCustom<P, D, R: PassKitRegistration, E: PassKitErrorLog
}

// MARK: - Push Notifications
public static func sendPushNotificationsForPass(id: UUID, of type: String, on db: any Database, app: Application) async throws {
let registrations = try await Self.registrationsForPass(id: id, of: type, on: db)
public static func sendPushNotificationsForPass(id: UUID, of passTypeIdentifier: String, on db: any Database, app: Application) async throws {
let registrations = try await Self.registrationsForPass(id: id, of: passTypeIdentifier, on: db)
for reg in registrations {
let backgroundNotification = APNSBackgroundNotification(expiration: .immediately, topic: reg.pass.passTypeIdentifier, payload: EmptyPayload())
do {
try await app.apns.client(.init(string: "passkit")).sendBackgroundNotification(
try await app.apns.client(.init(string: "passes")).sendBackgroundNotification(
backgroundNotification,
deviceToken: reg.device.pushToken
)
Expand Down
1 change: 1 addition & 0 deletions Sources/Passes/PassesDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import Vapor
import Fluent

/// The delegate which is responsible for generating the pass files.
public protocol PassesDelegate: AnyObject, Sendable {
/// Should return a `URL` which points to the template data for the pass.
///
Expand Down

0 comments on commit 33b80ac

Please sign in to comment.