Added the catalogue models and seed to support phase 6 of the POC prototype in the Persistence package.
This commit is contained in:
1 parent
31d2d98d49
commit
256d22df40
10 files changed
+473
-4
No files matched your search
@@ -32,6 +32,7 @@ public struct PrepareDB: Sendable {
|
||||
CreateTrade(),
|
||||
CreatePasskey(),
|
||||
CreateCollecting(),
|
||||
CreateCommunity(),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import Foundation
|
||||
|
||||
/// A `Sendable` snapshot of a con where shops trade in person, so a sale agreed on the site can be collected there.
|
||||
public struct Event: Sendable, Equatable {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The event's URL slug.
|
||||
public let slug: String
|
||||
/// The event's name.
|
||||
public let name: String
|
||||
/// Where it is held, e.g. `Jaarbeurs, Utrecht`.
|
||||
public let venue: String
|
||||
/// The first day.
|
||||
public let startsOn: Date
|
||||
/// The last day.
|
||||
public let endsOn: Date
|
||||
/// The shops trading there.
|
||||
public let shops: [Shop]
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
public init(
|
||||
slug: String,
|
||||
name: String,
|
||||
venue: String,
|
||||
startsOn: Date,
|
||||
endsOn: Date,
|
||||
shops: [Shop] = []
|
||||
) {
|
||||
self.slug = slug
|
||||
self.name = name
|
||||
self.venue = venue
|
||||
self.startsOn = startsOn
|
||||
self.endsOn = endsOn
|
||||
self.shops = shops
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,8 @@ public struct ListingFilter: Sendable, Equatable {
|
||||
public var verifiedShops: Bool
|
||||
/// The slug of the one shop to match, or `nil` for all shops.
|
||||
public var shop: String?
|
||||
/// The slugs of the shops to match — those trading at an event, say — or `nil` for all shops.
|
||||
public var shops: [String]?
|
||||
/// The order of the results.
|
||||
public var sort: Sort
|
||||
|
||||
@@ -51,6 +53,7 @@ public struct ListingFilter: Sendable, Equatable {
|
||||
acceptsOffers: Bool = false,
|
||||
verifiedShops: Bool = false,
|
||||
shop: String? = nil,
|
||||
shops: [String]? = nil,
|
||||
sort: Sort = .newest
|
||||
) {
|
||||
self.query = query
|
||||
@@ -67,6 +70,7 @@ public struct ListingFilter: Sendable, Equatable {
|
||||
self.acceptsOffers = acceptsOffers
|
||||
self.verifiedShops = verifiedShops
|
||||
self.shop = shop
|
||||
self.shops = shops
|
||||
self.sort = sort
|
||||
}
|
||||
|
||||
@@ -117,6 +121,10 @@ extension ListingFilter {
|
||||
return false
|
||||
}
|
||||
|
||||
if let shops, !shops.contains(listing.shop.slug) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !formats.isEmpty, !formats.contains(listing.format) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -133,6 +133,27 @@ public struct AccountRepository: Sendable {
|
||||
).save(on: fluent.db())
|
||||
}
|
||||
|
||||
/// Replaces every passkey of an account with a new one: what recovering an account comes down to.
|
||||
public func replacePasskeys(
|
||||
of user: User,
|
||||
credentialID: String,
|
||||
publicKey: [UInt8],
|
||||
signCount: UInt32
|
||||
) async throws {
|
||||
try await fluent.db().transaction { database in
|
||||
try await PasskeyRecord.query(on: database)
|
||||
.filter(\.$user.$id == user.id)
|
||||
.delete()
|
||||
try await PasskeyRecord(
|
||||
userID: user.id,
|
||||
credentialID: credentialID,
|
||||
publicKey: publicKey,
|
||||
signCount: signCount,
|
||||
at: .init()
|
||||
).save(on: database)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the passkeys registered to an account, the oldest first, with when each was created.
|
||||
public func passkeys(
|
||||
of user: User
|
||||
|
||||
@@ -192,3 +192,105 @@ public extension CollectingRepository {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Collection
|
||||
|
||||
public extension CollectingRepository {
|
||||
|
||||
/// Adds issues of a series to an account's collection; the ones already in it, or outside the run, are skipped.
|
||||
func own(
|
||||
issues: [Int],
|
||||
ofSeries slug: String,
|
||||
by user: User
|
||||
) async throws {
|
||||
guard let series = try await series(slug: slug) else {
|
||||
return
|
||||
}
|
||||
|
||||
let owned = try await ownedIssues(of: user)[slug] ?? []
|
||||
let run = series.issueCount > 0 ? 1...series.issueCount : 1...999
|
||||
|
||||
for issue in Set(issues).subtracting(owned) where run.contains(issue) {
|
||||
try await OwnedIssueRecord(userID: user.id, seriesSlug: slug, issue: issue).save(on: fluent.db())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the issues an account owns, by series slug.
|
||||
func ownedIssues(
|
||||
of user: User
|
||||
) async throws -> [String: Set<Int>] {
|
||||
try await OwnedIssueRecord.query(on: fluent.db())
|
||||
.filter(\.$user.$id == user.id)
|
||||
.all()
|
||||
.reduce(into: [:]) { $0[$1.seriesSlug, default: []].insert($1.issue) }
|
||||
}
|
||||
|
||||
/// Takes an issue out of an account's collection.
|
||||
func disown(
|
||||
issue: Int,
|
||||
ofSeries slug: String,
|
||||
by user: User
|
||||
) async throws {
|
||||
try await OwnedIssueRecord.query(on: fluent.db())
|
||||
.filter(\.$user.$id == user.id)
|
||||
.filter(\.$seriesSlug == slug)
|
||||
.filter(\.$issue == issue)
|
||||
.delete()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Events
|
||||
|
||||
public extension CollectingRepository {
|
||||
|
||||
/// Fetches the events still to come or under way, soonest first, each with the shops trading there.
|
||||
func upcomingEvents() async throws -> [Event] {
|
||||
try await EventRecord.query(on: fluent.db())
|
||||
.filter(\.$endsOn >= now().addingTimeInterval(-86_400))
|
||||
.sort(\.$startsOn)
|
||||
.with(\.$shops)
|
||||
.all()
|
||||
.map { record in
|
||||
.init(
|
||||
slug: record.slug,
|
||||
name: record.name,
|
||||
venue: record.venue,
|
||||
startsOn: record.startsOn,
|
||||
endsOn: record.endsOn,
|
||||
shops: record.shops.map(\.snapshot)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the upcoming events the shop an account owns trades at.
|
||||
func setEvents(
|
||||
_ slugs: [String],
|
||||
forShopOwnedBy user: User
|
||||
) async throws {
|
||||
try await fluent.db().transaction { database in
|
||||
guard let shop = try await ShopRecord.query(on: database)
|
||||
.filter(\.$owner.$id == user.id)
|
||||
.first()
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let upcoming = try await EventRecord.query(on: database)
|
||||
.filter(\.$endsOn >= now().addingTimeInterval(-86_400))
|
||||
.with(\.$shops)
|
||||
.all()
|
||||
|
||||
for event in upcoming {
|
||||
let trades = try event.shops.contains { try $0.requireID() == shop.requireID() }
|
||||
|
||||
if slugs.contains(event.slug), !trades {
|
||||
try await event.$shops.attach(shop, on: database)
|
||||
} else if !slugs.contains(event.slug), trades {
|
||||
try await event.$shops.detach(shop, on: database)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user