v0.6.0 — Phase β-5 Marketplace
Voller Marketplace-Flow mit TabBar und Universal-Link-Handler. Drei Live-Decks (Geografie, English A2, Periodensystem) sind browse-, abonnier- und lernbar. - PublicDeckEntry/PublicDeck/PublicDeckVersion/PublicDeckOwner/ PublicDeckDetail Codable mit snake_case - ExploreResponse, BrowseResponse, SubscribeResponse - MarketplaceSort-Enum (recent/popular/trending) - CardsAPI.explore/browseMarketplace/publicDeck/subscribe/unsubscribe - MarketplaceStore @Observable mit Explore + Browse States - ExploreView: Featured + Trending Horizontal-Carousels, Browse-Link - BrowseView: Searchable + Sort-Picker + List - PublicDeckView: Header/Metadata/Subscribe — Subscribe löst Auto-Fork serverseitig aus, Response liefert private_deck_id, NavigationLink zum eigenen Deck - PublicDeckCard + BrowseRow mit forest-Theme - RootView: TabBar (Decks/Entdecken/Account) statt Single-View - Universal-Link-Handler: onOpenURL + onContinueUserActivity für https://cardecky.mana.how/d/<slug> und cards://d/<slug> - associated-domains: applinks:cardecky.mana.how im entitlement - 5 neue Marketplace-Decoding-Tests (35 Total grün) Universal-Links funktionieren erst nach AASA-Setup auf cardecky.mana.how/.well-known/apple-app-site-association (Web-Aufgabe, heute 404). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
80eb3708b4
commit
07ada72b0f
10 changed files with 1015 additions and 24 deletions
|
|
@ -54,6 +54,72 @@ actor CardsAPI {
|
|||
return try decoder.decode(DueReviewsResponse.self, from: data).total
|
||||
}
|
||||
|
||||
// MARK: - Marketplace
|
||||
|
||||
/// `GET /api/v1/marketplace/explore` — Featured + Trending.
|
||||
func explore() async throws -> ExploreResponse {
|
||||
let (data, http) = try await transport.request(path: "/api/v1/marketplace/explore")
|
||||
try ensureOK(http, data: data)
|
||||
return try decoder.decode(ExploreResponse.self, from: data)
|
||||
}
|
||||
|
||||
/// `GET /api/v1/marketplace/decks` — Browse mit Filtern.
|
||||
func browseMarketplace(
|
||||
query: String? = nil,
|
||||
sort: MarketplaceSort = .recent,
|
||||
language: String? = nil,
|
||||
limit: Int = 20,
|
||||
offset: Int = 0
|
||||
) async throws -> BrowseResponse {
|
||||
var items: [URLQueryItem] = [
|
||||
.init(name: "sort", value: sort.rawValue),
|
||||
.init(name: "limit", value: "\(limit)"),
|
||||
.init(name: "offset", value: "\(offset)"),
|
||||
]
|
||||
if let query, !query.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
items.append(.init(name: "q", value: query))
|
||||
}
|
||||
if let language {
|
||||
items.append(.init(name: "language", value: language))
|
||||
}
|
||||
var components = URLComponents()
|
||||
components.queryItems = items
|
||||
let queryString = components.percentEncodedQuery ?? ""
|
||||
let path = "/api/v1/marketplace/decks?\(queryString)"
|
||||
|
||||
let (data, http) = try await transport.request(path: path)
|
||||
try ensureOK(http, data: data)
|
||||
return try decoder.decode(BrowseResponse.self, from: data)
|
||||
}
|
||||
|
||||
/// `GET /api/v1/marketplace/decks/:slug`.
|
||||
func publicDeck(slug: String) async throws -> PublicDeckDetail {
|
||||
let (data, http) = try await transport.request(path: "/api/v1/marketplace/decks/\(slug)")
|
||||
try ensureOK(http, data: data)
|
||||
return try decoder.decode(PublicDeckDetail.self, from: data)
|
||||
}
|
||||
|
||||
/// `POST /api/v1/marketplace/decks/:slug/subscribe` — Auto-Fork
|
||||
/// passiert serverseitig, Response liefert `private_deck_id`.
|
||||
@discardableResult
|
||||
func subscribe(slug: String) async throws -> SubscribeResponse {
|
||||
let (data, http) = try await transport.request(
|
||||
path: "/api/v1/marketplace/decks/\(slug)/subscribe",
|
||||
method: "POST"
|
||||
)
|
||||
try ensureOK(http, data: data)
|
||||
return try decoder.decode(SubscribeResponse.self, from: data)
|
||||
}
|
||||
|
||||
/// `DELETE /api/v1/marketplace/decks/:slug/subscribe`.
|
||||
func unsubscribe(slug: String) async throws {
|
||||
let (data, http) = try await transport.request(
|
||||
path: "/api/v1/marketplace/decks/\(slug)/subscribe",
|
||||
method: "DELETE"
|
||||
)
|
||||
try ensureOK(http, data: data)
|
||||
}
|
||||
|
||||
// MARK: - Media
|
||||
|
||||
/// `POST /api/v1/media/upload` — Multipart-Upload. Max 25 MiB.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue