Skip to content

Commit

Permalink
Start work on the Orders target
Browse files Browse the repository at this point in the history
  • Loading branch information
fpseverino committed Jun 30, 2024
1 parent d21c482 commit 75735b9
Show file tree
Hide file tree
Showing 11 changed files with 424 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,19 @@ let package = Package(
],
swiftSettings: swiftSettings
),
.target(
name: "Orders",
dependencies: [
.target(name: "PassKit"),
],
swiftSettings: swiftSettings
),
.testTarget(
name: "PassKitTests",
dependencies: [
.target(name: "PassKit"),
.target(name: "Passes"),
.target(name: "Orders"),
.product(name: "XCTVapor", package: "vapor"),
],
swiftSettings: swiftSettings
Expand Down
18 changes: 18 additions & 0 deletions Sources/Orders/DTOs/OrdersForDeviceDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// OrdersForDeviceDTO.swift
// PassKit
//
// Created by Francesco Paolo Severino on 30/06/24.
//

import Vapor

struct OrdersForDeviceDTO: Content {
let orderIdentifiers: [String]
let lastModified: String

init(with orderIdentifiers: [String], maxDate: Date) {
self.orderIdentifiers = orderIdentifiers
lastModified = String(maxDate.timeIntervalSince1970)
}
}
22 changes: 22 additions & 0 deletions Sources/Orders/Middleware/AppleOrderMiddleware.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// AppleOrderMiddleware.swift
// PassKit
//
// Created by Francesco Paolo Severino on 30/06/24.
//

import Vapor
import FluentKit

struct AppleOrderMiddleware<O: OrderModel>: AsyncMiddleware {
func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response {
guard let auth = request.headers["Authorization"].first?.replacingOccurrences(of: "AppleOrder ", with: ""),
let _ = try await O.query(on: request.db)
.filter(\._$authenticationToken == auth)
.first()
else {
throw Abort(.unauthorized)
}
return try await next.respond(to: request)
}
}
57 changes: 57 additions & 0 deletions Sources/Orders/Models/Concrete Models/Order.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// Order.swift
// PassKit
//
// Created by Francesco Paolo Severino on 30/06/24.
//

import Foundation
import FluentKit

/// The `Model` that stores Wallet orders.
open class Order: OrderModel, @unchecked Sendable {
public static let schema = Order.FieldKeys.schemaName

@ID
public var id: UUID?

@Timestamp(key: Order.FieldKeys.updatedAt, on: .update)
public var updatedAt: Date?

@Field(key: Order.FieldKeys.orderTypeIdentifier)
public var orderTypeIdentifier: String

@Field(key: Order.FieldKeys.authenticationToken)
public var authenticationToken: String

public required init() { }

public required init(orderTypeIdentifier: String, authenticationToken: String) {
self.orderTypeIdentifier = orderTypeIdentifier
self.authenticationToken = authenticationToken
}
}

extension Order: AsyncMigration {
public func prepare(on database: any Database) async throws {
try await database.schema(Self.schema)
.id()
.field(Order.FieldKeys.updatedAt, .datetime, .required)
.field(Order.FieldKeys.orderTypeIdentifier, .string, .required)
.field(Order.FieldKeys.authenticationToken, .string, .required)
.create()
}

public func revert(on database: any Database) async throws {
try await database.schema(Self.schema).delete()
}
}

extension Order {
enum FieldKeys {
static let schemaName = "orders"
static let updatedAt = FieldKey(stringLiteral: "updated_at")
static let orderTypeIdentifier = FieldKey(stringLiteral: "order_type_identifier")
static let authenticationToken = FieldKey(stringLiteral: "authentication_token")
}
}
53 changes: 53 additions & 0 deletions Sources/Orders/Models/Concrete Models/OrdersDevice.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// OrdersDevice.swift
// PassKit
//
// Created by Francesco Paolo Severino on 30/06/24.
//

import FluentKit
import PassKit

/// The `Model` that stores Wallet orders devices.
final public class OrdersDevice: DeviceModel, @unchecked Sendable {
public static let schema = OrdersDevice.FieldKeys.schemaName

@ID(custom: .id)
public var id: Int?

@Field(key: OrdersDevice.FieldKeys.pushToken)
public var pushToken: String

@Field(key: OrdersDevice.FieldKeys.deviceLibraryIdentifier)
public var deviceLibraryIdentifier: String

public init(deviceLibraryIdentifier: String, pushToken: String) {
self.deviceLibraryIdentifier = deviceLibraryIdentifier
self.pushToken = pushToken
}

public init() {}
}

extension OrdersDevice: AsyncMigration {
public func prepare(on database: any Database) async throws {
try await database.schema(Self.schema)
.field(.id, .int, .identifier(auto: true))
.field(OrdersDevice.FieldKeys.pushToken, .string, .required)
.field(OrdersDevice.FieldKeys.deviceLibraryIdentifier, .string, .required)
.unique(on: OrdersDevice.FieldKeys.pushToken, OrdersDevice.FieldKeys.deviceLibraryIdentifier)
.create()
}

public func revert(on database: any Database) async throws {
try await database.schema(Self.schema).delete()
}
}

extension OrdersDevice {
enum FieldKeys {
static let schemaName = "orders_devices"
static let pushToken = FieldKey(stringLiteral: "push_token")
static let deviceLibraryIdentifier = FieldKey(stringLiteral: "device_library_identifier")
}
}
52 changes: 52 additions & 0 deletions Sources/Orders/Models/Concrete Models/OrdersErrorLog.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// OrdersErrorLog.swift
// PassKit
//
// Created by Francesco Paolo Severino on 30/06/24.
//

import struct Foundation.Date
import FluentKit
import PassKit

/// The `Model` that stores Wallet orders error logs.
final public class OrdersErrorLog: ErrorLogModel, @unchecked Sendable {
public static let schema = OrdersErrorLog.FieldKeys.schemaName

@ID(custom: .id)
public var id: Int?

@Timestamp(key: OrdersErrorLog.FieldKeys.createdAt, on: .create)
public var createdAt: Date?

@Field(key: OrdersErrorLog.FieldKeys.message)
public var message: String

public init(message: String) {
self.message = message
}

public init() {}
}

extension OrdersErrorLog: AsyncMigration {
public func prepare(on database: any Database) async throws {
try await database.schema(Self.schema)
.field(.id, .int, .identifier(auto: true))
.field(OrdersErrorLog.FieldKeys.createdAt, .datetime, .required)
.field(OrdersErrorLog.FieldKeys.message, .string, .required)
.create()
}

public func revert(on database: any Database) async throws {
try await database.schema(Self.schema).delete()
}
}

extension OrdersErrorLog {
enum FieldKeys {
static let schemaName = "orders_errors"
static let createdAt = FieldKey(stringLiteral: "created_at")
static let message = FieldKey(stringLiteral: "message")
}
}
49 changes: 49 additions & 0 deletions Sources/Orders/Models/Concrete Models/OrdersRegistration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// OrdersRegistration.swift
// PassKit
//
// Created by Francesco Paolo Severino on 30/06/24.
//

import FluentKit

/// The `Model` that stores orders registrations.
final public class OrdersRegistration: OrdersRegistrationModel, @unchecked Sendable {
public typealias OrderType = Order
public typealias DeviceType = OrdersDevice

public static let schema = OrdersRegistration.FieldKeys.schemaName

@ID(custom: .id)
public var id: Int?

@Parent(key: OrdersRegistration.FieldKeys.deviceID)
public var device: DeviceType

@Parent(key: OrdersRegistration.FieldKeys.orderID)
public var order: OrderType

public init() {}
}

extension OrdersRegistration: AsyncMigration {
public func prepare(on database: any Database) async throws {
try await database.schema(Self.schema)
.field(.id, .int, .identifier(auto: true))
.field(OrdersRegistration.FieldKeys.deviceID, .int, .required, .references(DeviceType.schema, .id, onDelete: .cascade))
.field(OrdersRegistration.FieldKeys.orderID, .uuid, .required, .references(OrderType.schema, .id, onDelete: .cascade))
.create()
}

public func revert(on database: any Database) async throws {
try await database.schema(Self.schema).delete()
}
}

extension OrdersRegistration {
enum FieldKeys {
static let schemaName = "orders_registrations"
static let deviceID = FieldKey(stringLiteral: "device_id")
static let orderID = FieldKey(stringLiteral: "order_id")
}
}
27 changes: 27 additions & 0 deletions Sources/Orders/Models/OrderDataModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// OrderDataModel.swift
// PassKit
//
// Created by Francesco Paolo Severino on 30/06/24.
//

import FluentKit

/// Represents the `Model` that stores custom app data associated to Wallet orders.
public protocol OrderDataModel: Model {
associatedtype OrderType: OrderModel

/// The foreign key to the order table
var order: OrderType { get set }
}

internal extension OrderDataModel {
var _$order: Parent<OrderType> {
guard let mirror = Mirror(reflecting: self).descendant("_order"),
let order = mirror as? Parent<OrderType> else {
fatalError("order property must be declared using @Parent")
}

return order
}
}
61 changes: 61 additions & 0 deletions Sources/Orders/Models/OrderModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// OrderModel.swift
// PassKit
//
// Created by Francesco Paolo Severino on 30/06/24.
//

import Foundation
import FluentKit

/// Represents the `Model` that stores Waller orders.
///
/// Uses a UUID so people can't easily guess order IDs
public protocol OrderModel: Model where IDValue == UUID {
/// The order type identifier.
var orderTypeIdentifier: String { get set }

/// The last time the order was modified.
var updatedAt: Date? { get set }

/// The authentication token for the order.
var authenticationToken: String { get set }
}

internal extension OrderModel {
var _$id: ID<UUID> {
guard let mirror = Mirror(reflecting: self).descendant("_id"),
let id = mirror as? ID<UUID> else {
fatalError("id property must be declared using @ID")
}

return id
}

var _$orderTypeIdentifier: Field<String> {
guard let mirror = Mirror(reflecting: self).descendant("_orderTypeIdentifier"),
let orderTypeIdentifier = mirror as? Field<String> else {
fatalError("orderTypeIdentifier property must be declared using @Field")
}

return orderTypeIdentifier
}

var _$updatedAt: Timestamp<DefaultTimestampFormat> {
guard let mirror = Mirror(reflecting: self).descendant("_updatedAt"),
let updatedAt = mirror as? Timestamp<DefaultTimestampFormat> else {
fatalError("updatedAt property must be declared using @Timestamp(on: .update)")
}

return updatedAt
}

var _$authenticationToken: Field<String> {
guard let mirror = Mirror(reflecting: self).descendant("_authenticationToken"),
let authenticationToken = mirror as? Field<String> else {
fatalError("authenticationToken property must be declared using @Field")
}

return authenticationToken
}
}
Loading

0 comments on commit 75735b9

Please sign in to comment.