From 6c23daaaba2a954220ece2afa7193dbdcad55c22 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 22 Apr 2026 13:47:37 -0500 Subject: feat(v3.2.0): add Data+ tier and external data integrations - introduce Data+ tier for advanced data features - add ownership history and DNS history - expand subdomain discovery with external sources - add domain pricing insights - implement local usage/credit system - integrate external data service layer with rate limiting - extend export and CLI for Data+ features --- DomainDig/UsageCreditService.swift | 73 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 DomainDig/UsageCreditService.swift (limited to 'DomainDig/UsageCreditService.swift') diff --git a/DomainDig/UsageCreditService.swift b/DomainDig/UsageCreditService.swift new file mode 100644 index 0000000..48f40be --- /dev/null +++ b/DomainDig/UsageCreditService.swift @@ -0,0 +1,73 @@ +import Foundation + +actor UsageCreditService { + static let shared = UsageCreditService() + + private struct CreditLedger: Codable { + let appVersion: String + var remainingByFeature: [UsageCreditFeature: Int] + } + + private let storageKey = "usageCredits.ledger" + private var ledger: CreditLedger + + init(defaults: UserDefaults = .standard) { + if let data = defaults.data(forKey: storageKey), + let decoded = try? JSONDecoder().decode(CreditLedger.self, from: data), + decoded.appVersion == AppVersion.current { + ledger = decoded + } else { + ledger = Self.makeLedger() + if let data = try? JSONEncoder().encode(ledger) { + defaults.set(data, forKey: storageKey) + } + } + } + + func status(for feature: UsageCreditFeature) -> UsageCreditStatus { + let total = feature.defaultAllowance + let remaining = ledger.remainingByFeature[feature] ?? total + return UsageCreditStatus( + feature: feature, + remaining: remaining, + total: total, + resetContext: "Resets with app version \(ledger.appVersion)" + ) + } + + func allStatuses() -> [UsageCreditStatus] { + UsageCreditFeature.allCases.map { status(for: $0) } + } + + func canUse(_ feature: UsageCreditFeature) -> Bool { + status(for: feature).remaining > 0 + } + + @discardableResult + func consume(_ feature: UsageCreditFeature) -> UsageCreditStatus { + let current = ledger.remainingByFeature[feature] ?? feature.defaultAllowance + ledger.remainingByFeature[feature] = max(0, current - 1) + persist() + return status(for: feature) + } + + func resetForCurrentVersion() { + ledger = Self.makeLedger() + persist() + } + + private static func makeLedger() -> CreditLedger { + CreditLedger( + appVersion: AppVersion.current, + remainingByFeature: Dictionary( + uniqueKeysWithValues: UsageCreditFeature.allCases.map { ($0, $0.defaultAllowance) } + ) + ) + } + + private func persist(defaults: UserDefaults = .standard) { + if let data = try? JSONEncoder().encode(ledger) { + defaults.set(data, forKey: storageKey) + } + } +} -- cgit v1.2.3