mana-swift-core/Sources/ManaCore/Credits/ManaCredits.swift
Till JS 406d452855 feat: ManaCreditsClient — ökosystemweites Mana-Guthaben (ManaCore)
Liest /api/v1/credits/{balance,transactions} von credits.mana.how (mana-auth-JWT),
Pendant zu @mana/mana-credits-client. baseURL überschreibbar. Basis der nativen
Gesamt-Mana-Anzeige (ManaCreditsUI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:33:02 +02:00

81 lines
2.9 KiB
Swift

import Foundation
/// Mana-Guthaben eines Nutzers — alle Werte in Mana (1 Mana = 1 Cent).
/// Wire-Mirror von `GET /api/v1/credits/balance` (mana-credits). Das
/// Guthaben ist EIN Wert pro Nutzer, app-übergreifend — die ökosystemweite
/// „Gesamt-Mana-Anzeige".
public struct ManaCreditBalance: Decodable, Sendable, Equatable {
public let balance: Int
public let totalEarned: Int
public let totalSpent: Int
public init(balance: Int, totalEarned: Int, totalSpent: Int) {
self.balance = balance
self.totalEarned = totalEarned
self.totalSpent = totalSpent
}
}
/// Eine Mana-Bewegung. Wire-Mirror von `GET /api/v1/credits/transactions`.
/// `amount` ist vorzeichenbehaftet (Gutschrift positiv, Ausgabe negativ).
public struct ManaCreditTransaction: Decodable, Sendable, Identifiable {
public let id: String
public let type: String
public let status: String
public let amount: Int
public let balanceBefore: Int
public let balanceAfter: Int
public let appId: String
public let description: String
public let createdAt: String
public let completedAt: String?
}
/// Fehler des Credits-Clients.
public enum ManaCreditsError: Error, LocalizedError {
case http(Int)
public var errorDescription: String? {
switch self {
case let .http(code): "mana-credits antwortete mit HTTP \(code)."
}
}
}
/// Client für das ökosystemweite Mana-Guthaben (mana-credits-Service).
///
/// Spricht standardmäßig `credits.mana.how` mit dem mana-auth-JWT — genau
/// wie das Web-Pendant `@mana/mana-credits-client`. Das Guthaben gilt
/// app-übergreifend; jede native mana-App kann es so anzeigen.
///
/// `baseURL` ist überschreibbar (App-Config / Tests); die Endpoint-Pfade
/// (`/api/v1/credits/*`) sind Verein-Plattform-Konstanten.
public struct ManaCreditsClient: Sendable {
private let transport: AuthenticatedTransport
public init(
auth: AuthClient,
baseURL: URL = URL(string: "https://credits.mana.how")!
) {
transport = AuthenticatedTransport(baseURL: baseURL, auth: auth)
}
/// `GET /api/v1/credits/balance` — aktuelles Mana-Guthaben.
public func balance() async throws -> ManaCreditBalance {
try await get("/api/v1/credits/balance")
}
/// `GET /api/v1/credits/transactions` — letzte Mana-Bewegungen.
public func transactions(limit: Int = 25, offset: Int = 0) async throws -> [ManaCreditTransaction] {
try await get("/api/v1/credits/transactions?limit=\(limit)&offset=\(offset)")
}
private func get<T: Decodable>(_ path: String) async throws -> T {
let (data, http) = try await transport.request(path: path)
guard (200 ..< 300).contains(http.statusCode) else {
throw ManaCreditsError.http(http.statusCode)
}
// JSONDecoder lokal erzeugen — hält den Client `Sendable`.
return try JSONDecoder().decode(T.self, from: data)
}
}