import Foundation /// Browse-Eintrag aus `/api/v1/marketplace/decks` und `.../explore`. struct PublicDeckEntry: Codable, Hashable, Identifiable { let slug: String let title: String let description: String? let language: String? let category: String? let license: String let priceCredits: Int let cardCount: Int let starCount: Int let subscriberCount: Int let isFeatured: Bool let createdAt: Date let owner: PublicDeckOwner var id: String { slug } enum CodingKeys: String, CodingKey { case slug, title, description, language, category, license case priceCredits = "price_credits" case cardCount = "card_count" case starCount = "star_count" case subscriberCount = "subscriber_count" case isFeatured = "is_featured" case createdAt = "created_at" case owner } var isPaid: Bool { priceCredits > 0 } } struct PublicDeckOwner: Codable, Hashable { let slug: String let displayName: String let verifiedMana: Bool let verifiedCommunity: Bool /// Flag: Autor tritt pseudonym auf (Anzeigename verbergen). /// Server-Schema ist `boolean NOT NULL DEFAULT false`. War in v0.8.x /// fälschlich als `String?` (Anzeige-Pseudonym) interpretiert — /// führte zu Decoder-typeMismatch beim Öffnen eines Marketplace-Decks. let pseudonym: Bool enum CodingKeys: String, CodingKey { case slug case displayName = "display_name" case verifiedMana = "verified_mana" case verifiedCommunity = "verified_community" case pseudonym } init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) slug = try c.decode(String.self, forKey: .slug) displayName = try c.decode(String.self, forKey: .displayName) verifiedMana = try c.decode(Bool.self, forKey: .verifiedMana) verifiedCommunity = try c.decode(Bool.self, forKey: .verifiedCommunity) pseudonym = (try? c.decode(Bool.self, forKey: .pseudonym)) ?? false } } /// Response von `GET /api/v1/marketplace/explore`. struct ExploreResponse: Decodable { let featured: [PublicDeckEntry] let trending: [PublicDeckEntry] } /// Response von `GET /api/v1/marketplace/decks`. struct BrowseResponse: Decodable { let items: [PublicDeckEntry] let total: Int } /// Vollständiges Public-Deck aus `GET /api/v1/marketplace/decks/:slug`. struct PublicDeck: Codable, Hashable, Identifiable { let id: String let slug: String let title: String let description: String? let language: String? let category: String? let license: String let priceCredits: Int let ownerUserId: String let latestVersionId: String? let isFeatured: Bool let isTakedown: Bool let createdAt: Date enum CodingKeys: String, CodingKey { case id, slug, title, description, language, category, license case priceCredits = "price_credits" case ownerUserId = "owner_user_id" case latestVersionId = "latest_version_id" case isFeatured = "is_featured" case isTakedown = "is_takedown" case createdAt = "created_at" } } struct PublicDeckVersion: Codable, Hashable, Identifiable { let id: String let deckId: String let semver: String let changelog: String? let contentHash: String? let cardCount: Int let publishedAt: Date let deprecatedAt: Date? enum CodingKeys: String, CodingKey { case id case deckId = "deck_id" case semver case changelog case contentHash = "content_hash" case cardCount = "card_count" case publishedAt = "published_at" case deprecatedAt = "deprecated_at" } } /// Response von `GET /api/v1/marketplace/decks/:slug`. struct PublicDeckDetail: Decodable { let deck: PublicDeck let latestVersion: PublicDeckVersion? let owner: PublicDeckOwner? enum CodingKeys: String, CodingKey { case deck case latestVersion = "latest_version" case owner } } /// Response von `POST /api/v1/marketplace/decks/:slug/subscribe`. struct SubscribeResponse: Decodable { let subscribed: Bool let deckSlug: String let currentVersionId: String? let privateDeckId: String enum CodingKeys: String, CodingKey { case subscribed case deckSlug = "deck_slug" case currentVersionId = "current_version_id" case privateDeckId = "private_deck_id" } } /// Browse-Sort-Optionen aus `BrowseQuerySchema`. enum MarketplaceSort: String, CaseIterable { case recent case popular case trending var label: String { switch self { case .recent: "Neueste" case .popular: "Beliebt" case .trending: "Im Trend" } } }