Added the catalogue models and seed to support phase 4 of the POC prototype in the Persistence package.

This commit is contained in:
javier committed 2026-09-21 12:50:53 +02:00
1 parent 0dc3fe6bea
commit 95e0a455e8
14 files changed
+743 -9

No files matched your search

@@ -0,0 +1,29 @@
import FluentKit
/// Adds the declared defects and the photos to listings.
///
/// Registered ahead of ``SeedCatalogue``, whose model writes both columns.
struct AddListingDeclarations: AsyncMigration {
// MARK: Methods
func prepare(on database: Database) async throws {
// One column per update: SQLite adds no more in a single statement.
try await database.schema(ListingRecord.schema)
.field("defects", .string)
.update()
try await database.schema(ListingRecord.schema)
.field("photos", .string)
.update()
}
func revert(on database: Database) async throws {
try await database.schema(ListingRecord.schema)
.deleteField("photos")
.update()
try await database.schema(ListingRecord.schema)
.deleteField("defects")
.update()
}
}
@@ -0,0 +1,25 @@
import FluentKit
/// Creates and drops the `passkeys` table backing ``PasskeyRecord``.
struct CreatePasskey: AsyncMigration {
// MARK: Methods
func prepare(on database: Database) async throws {
try await database.schema(PasskeyRecord.schema)
.id()
.field("user_id", .uuid, .required, .references(UserRecord.schema, "id", onDelete: .cascade))
.field("credential_id", .string, .required)
.field("public_key", .string, .required)
.field("sign_count", .int, .required)
.field("created_at", .datetime, .required)
.unique(on: "credential_id")
.create()
}
func revert(on database: Database) async throws {
try await database.schema(PasskeyRecord.schema)
.delete()
}
}
@@ -94,6 +94,14 @@ final class ListingRecord: Model, @unchecked Sendable {
@OptionalField(key: "sold_at")
var soldAt: Date?
/// The declared defects, one per line.
@OptionalField(key: "defects")
var defects: String?
/// The URL paths of the photos, one per line, the front first.
@OptionalField(key: "photos")
var photos: String?
// MARK: Initializers
init() {}
@@ -132,6 +140,8 @@ final class ListingRecord: Model, @unchecked Sendable {
self.coverImprint = listing.cover.imprint
self.coverNumber = listing.cover.number
self.listedAt = listing.listedAt
self.defects = listing.defects.isEmpty ? nil : listing.defects.joined(separator: "\n")
self.photos = listing.photos.isEmpty ? nil : listing.photos.joined(separator: "\n")
}
}
@@ -173,7 +183,9 @@ extension ListingRecord {
number: coverNumber
),
listedAt: listedAt,
soldAt: soldAt
soldAt: soldAt,
defects: defects?.split(separator: "\n").map(String.init) ?? [],
photos: photos?.split(separator: "\n").map(String.init) ?? []
)
}
@@ -0,0 +1,69 @@
import FluentKit
import Foundation
/// A FluentKit model of a single `passkeys` row: a WebAuthn credential registered to an account.
final class PasskeyRecord: Model, @unchecked Sendable {
// MARK: Properties
static let schema = "passkeys"
@ID(key: .id)
var id: UUID?
@Parent(key: "user_id")
var user: UserRecord
/// The credential's identifier, base64url-encoded without padding.
@Field(key: "credential_id")
var credentialID: String
/// The credential's public key, COSE-encoded, as base64.
@Field(key: "public_key")
var publicKey: String
@Field(key: "sign_count")
var signCount: Int
@Field(key: "created_at")
var createdAt: Date
// MARK: Initializers
init() {}
init(
userID: UUID,
credentialID: String,
publicKey: [UInt8],
signCount: UInt32,
at date: Date
) {
self.$user.id = userID
self.credentialID = credentialID
self.publicKey = Data(publicKey).base64EncodedString()
self.signCount = Int(signCount)
self.createdAt = date
}
}
// MARK: - Extensions
extension PasskeyRecord {
// MARK: Computed
/// The `Sendable` snapshot of the saved record; its user must be eager-loaded.
var snapshot: Passkey {
get throws {
.init(
credentialID: credentialID,
publicKey: Array(Data(base64Encoded: publicKey) ?? .init()),
signCount: UInt32(clamping: signCount),
user: try user.snapshot
)
}
}
}