195 lines
5.9 KiB
Swift
195 lines
5.9 KiB
Swift
import Foundation
|
|
|
|
/// A `Sendable` snapshot of one copy for sale, with the shop selling it.
|
|
public struct Listing: Sendable, Equatable {
|
|
|
|
// MARK: Properties
|
|
|
|
/// The listing's URL slug.
|
|
public let slug: String
|
|
/// The shop selling the copy.
|
|
public let shop: Shop
|
|
/// The listing's title, e.g. `Nightward #1`.
|
|
public let title: String
|
|
/// The series the copy belongs to, e.g. `Nightward (2019 series)`.
|
|
public let seriesTitle: String
|
|
/// The issue or volume, e.g. `#1, cover A`.
|
|
public let issueLabel: String
|
|
/// The publisher's name.
|
|
public let publisher: String
|
|
/// The cover year.
|
|
public let year: Int
|
|
/// The format token, e.g. `single-issues`.
|
|
public let format: String
|
|
/// The printing in a few words, e.g. `1st print`.
|
|
public let printing: String
|
|
/// The printing in full, e.g. `First printing, direct edition`.
|
|
public let printingDetail: String
|
|
/// Whether the copy is a first printing.
|
|
public let isFirstPrint: Bool
|
|
/// The company that graded and slabbed the copy, or `nil` for a raw copy.
|
|
public let grader: String?
|
|
/// The numeric grade, or `nil` for formats that are not graded.
|
|
public let grade: Double?
|
|
/// The condition as the seller states it, e.g. `NM` or `Like new`.
|
|
public let condition: String
|
|
/// The grader's certificate number, or `nil` for a raw copy.
|
|
public let certNumber: String?
|
|
/// The page quality, or `nil` when not stated.
|
|
public let pageQuality: String?
|
|
/// Why the issue is a key, or `nil` when it is not one.
|
|
public let keyNotes: String?
|
|
/// The seller's notes on the copy.
|
|
public let sellerNotes: String
|
|
/// The asking price, in euro cents.
|
|
public let priceCents: Int
|
|
/// The shipping cost to the Netherlands, in euro cents.
|
|
public let shippingCents: Int
|
|
/// Whether the seller takes offers on the copy.
|
|
public let acceptsOffers: Bool
|
|
/// The placeholder cover shown until listings carry photos.
|
|
public let cover: Cover
|
|
/// When the copy was listed.
|
|
public let listedAt: Date
|
|
/// When a sale of the copy was agreed, or `nil` while it is for sale.
|
|
public let soldAt: Date?
|
|
/// The defects the seller declared, from the grading guide's checklist.
|
|
public let defects: [String]
|
|
/// The URL paths of the photos of this copy, the front first; empty while the listing shows its placeholder cover.
|
|
public let photos: [String]
|
|
|
|
// MARK: Initializers
|
|
|
|
public init(
|
|
slug: String,
|
|
shop: Shop,
|
|
title: String,
|
|
seriesTitle: String,
|
|
issueLabel: String,
|
|
publisher: String,
|
|
year: Int,
|
|
format: String,
|
|
printing: String,
|
|
printingDetail: String,
|
|
isFirstPrint: Bool,
|
|
grader: String?,
|
|
grade: Double?,
|
|
condition: String,
|
|
certNumber: String?,
|
|
pageQuality: String?,
|
|
keyNotes: String?,
|
|
sellerNotes: String,
|
|
priceCents: Int,
|
|
shippingCents: Int,
|
|
acceptsOffers: Bool,
|
|
cover: Cover,
|
|
listedAt: Date,
|
|
soldAt: Date? = nil,
|
|
defects: [String] = [],
|
|
photos: [String] = []
|
|
) {
|
|
self.slug = slug
|
|
self.shop = shop
|
|
self.title = title
|
|
self.seriesTitle = seriesTitle
|
|
self.issueLabel = issueLabel
|
|
self.publisher = publisher
|
|
self.year = year
|
|
self.format = format
|
|
self.printing = printing
|
|
self.printingDetail = printingDetail
|
|
self.isFirstPrint = isFirstPrint
|
|
self.grader = grader
|
|
self.grade = grade
|
|
self.condition = condition
|
|
self.certNumber = certNumber
|
|
self.pageQuality = pageQuality
|
|
self.keyNotes = keyNotes
|
|
self.sellerNotes = sellerNotes
|
|
self.priceCents = priceCents
|
|
self.shippingCents = shippingCents
|
|
self.acceptsOffers = acceptsOffers
|
|
self.cover = cover
|
|
self.listedAt = listedAt
|
|
self.soldAt = soldAt
|
|
self.defects = defects
|
|
self.photos = photos
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Extensions
|
|
|
|
public extension Listing {
|
|
|
|
// MARK: Computed
|
|
|
|
/// What the seller declared of the copy's condition, in a sentence: the defects, then the notes. Frozen into a sale at agreement.
|
|
var declaration: String {
|
|
let defects = defects.isEmpty
|
|
? "No defects declared."
|
|
: "Declared defects: \(defects.joined(separator: "; "))."
|
|
|
|
return sellerNotes.isEmpty ? defects : "\(defects) \(sellerNotes)"
|
|
}
|
|
|
|
/// The grade or condition in a few words: `CGC 9.8` for a slab, `Raw · NM` for a raw issue, the condition alone for a collected edition.
|
|
var gradeLabel: String {
|
|
if let grader, let grade {
|
|
return "\(grader) \(String(format: "%.1f", grade))"
|
|
}
|
|
|
|
return format == "single-issues" ? "Raw · \(condition)" : condition
|
|
}
|
|
|
|
/// Whether the copy is still for sale.
|
|
var isAvailable: Bool {
|
|
soldAt == nil
|
|
}
|
|
|
|
/// Whether the issue is a key.
|
|
var isKey: Bool {
|
|
keyNotes != nil
|
|
}
|
|
|
|
/// Whether the copy is graded and slabbed by a grading company.
|
|
var isSlabbed: Bool {
|
|
grader != nil
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Structs
|
|
|
|
public extension Listing {
|
|
/// A placeholder cover: lettering on a toned ground.
|
|
struct Cover: Sendable, Equatable {
|
|
|
|
// MARK: Properties
|
|
|
|
/// The tone token naming the cover's colours, e.g. `navy`.
|
|
public let tone: String
|
|
/// The title lettering, one line per newline.
|
|
public let title: String
|
|
/// The publisher's imprint line.
|
|
public let imprint: String
|
|
/// The issue number or volume marker.
|
|
public let number: String
|
|
|
|
// MARK: Initializers
|
|
|
|
public init(
|
|
tone: String,
|
|
title: String,
|
|
imprint: String,
|
|
number: String
|
|
) {
|
|
self.tone = tone
|
|
self.title = title
|
|
self.imprint = imprint
|
|
self.number = number
|
|
}
|
|
|
|
}
|
|
}
|