summaryrefslogtreecommitdiff
path: root/DomainDig/FeatureAccessService.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-22 11:46:52 -0500
committerChristian Cleberg <[email protected]>2026-04-22 11:46:52 -0500
commit37b02e5ee360d61751951b77b7b556165a3af1fc (patch)
treec2e30f32c4e8d2fcdf7035f97e9e353f09859114 /DomainDig/FeatureAccessService.swift
parentceac99c05f180d9008b75c9329822466ff60255b (diff)
downloaddomain-dig-37b02e5ee360d61751951b77b7b556165a3af1fc.tar.gz
domain-dig-37b02e5ee360d61751951b77b7b556165a3af1fc.tar.bz2
domain-dig-37b02e5ee360d61751951b77b7b556165a3af1fc.zip
feat(v3.0.0): unify platform architecture and introduce feature tiers
* consolidate DomainReport as canonical data model * add feature tier system (free/pro/data+ scaffolding) * apply clean feature gating across workflows and tracking * refactor app structure for maintainability * standardize navigation and settings * add data lifecycle controls * ensure consistency across UI, export, and CLI * stabilize internal inspection API
Diffstat (limited to 'DomainDig/FeatureAccessService.swift')
-rw-r--r--DomainDig/FeatureAccessService.swift160
1 files changed, 160 insertions, 0 deletions
diff --git a/DomainDig/FeatureAccessService.swift b/DomainDig/FeatureAccessService.swift
new file mode 100644
index 0000000..5f380bb
--- /dev/null
+++ b/DomainDig/FeatureAccessService.swift
@@ -0,0 +1,160 @@
+import Foundation
+
+enum FeatureTier: String, Codable, CaseIterable, Identifiable {
+ case free
+ case pro
+ case dataPlus
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .free:
+ return "Free"
+ case .pro:
+ return "Pro"
+ case .dataPlus:
+ return "Data+"
+ }
+ }
+}
+
+enum FeatureCapability: String, CaseIterable, Identifiable {
+ case singleLookup
+ case basicHistory
+ case limitedTracking
+ case workflows
+ case batchOperations
+ case advancedExports
+ case ownershipHistory
+ case dnsHistory
+ case extendedSubdomains
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .singleLookup:
+ return "Single lookup"
+ case .basicHistory:
+ return "Basic history"
+ case .limitedTracking:
+ return "Limited tracking"
+ case .workflows:
+ return "Workflows"
+ case .batchOperations:
+ return "Batch operations"
+ case .advancedExports:
+ return "Advanced exports"
+ case .ownershipHistory:
+ return "Ownership history"
+ case .dnsHistory:
+ return "DNS history"
+ case .extendedSubdomains:
+ return "Extended subdomains"
+ }
+ }
+}
+
+struct FeatureEntitlements: Equatable {
+ let tier: FeatureTier
+ let capabilities: Set<FeatureCapability>
+ let trackedDomainLimit: Int
+ let workflowLimit: Int?
+ let batchSizeLimit: Int?
+}
+
+enum FeatureAccessService {
+ static let currentTier: FeatureTier = .free
+
+ static var entitlements: FeatureEntitlements {
+ switch currentTier {
+ case .free:
+ return FeatureEntitlements(
+ tier: .free,
+ capabilities: [.singleLookup, .basicHistory, .limitedTracking],
+ trackedDomainLimit: 3,
+ workflowLimit: 0,
+ batchSizeLimit: 0
+ )
+ case .pro:
+ return FeatureEntitlements(
+ tier: .pro,
+ capabilities: [.singleLookup, .basicHistory, .limitedTracking, .workflows, .batchOperations, .advancedExports],
+ trackedDomainLimit: 250,
+ workflowLimit: 50,
+ batchSizeLimit: 100
+ )
+ case .dataPlus:
+ return FeatureEntitlements(
+ tier: .dataPlus,
+ capabilities: Set(FeatureCapability.allCases),
+ trackedDomainLimit: 1_000,
+ workflowLimit: 200,
+ batchSizeLimit: 250
+ )
+ }
+ }
+
+ static func hasAccess(to capability: FeatureCapability) -> Bool {
+ entitlements.capabilities.contains(capability)
+ }
+
+ static func canAddTrackedDomain(currentCount: Int) -> Bool {
+ currentCount < entitlements.trackedDomainLimit
+ }
+
+ static func trackedDomainLimitMessage(currentCount: Int) -> String? {
+ guard currentTier == .free else { return nil }
+ return currentCount >= entitlements.trackedDomainLimit
+ ? "Free includes up to \(entitlements.trackedDomainLimit) tracked domains."
+ : "Free includes up to \(entitlements.trackedDomainLimit) tracked domains."
+ }
+
+ static func canCreateWorkflow(currentCount: Int) -> Bool {
+ guard hasAccess(to: .workflows) else { return false }
+ guard let limit = entitlements.workflowLimit else { return true }
+ return currentCount < limit
+ }
+
+ static func canRunBatch(domainCount: Int) -> Bool {
+ guard hasAccess(to: .batchOperations) else { return false }
+ guard let limit = entitlements.batchSizeLimit else { return true }
+ return domainCount <= limit
+ }
+
+ static func upgradeMessage(for capability: FeatureCapability) -> String {
+ switch capability {
+ case .workflows, .batchOperations, .advancedExports:
+ return "Available in Pro"
+ case .ownershipHistory, .dnsHistory, .extendedSubdomains:
+ return "Available in Data+"
+ case .limitedTracking:
+ return "Tracking is limited on Free"
+ case .singleLookup, .basicHistory:
+ return "Included in Free"
+ }
+ }
+
+ static func workflowLimitMessage(currentCount: Int) -> String? {
+ guard hasAccess(to: .workflows) else {
+ return upgradeMessage(for: .workflows)
+ }
+ guard let limit = entitlements.workflowLimit, currentCount >= limit else { return nil }
+ return "Workflow limit reached."
+ }
+
+ static func batchLimitMessage(domainCount: Int) -> String? {
+ guard hasAccess(to: .batchOperations) else {
+ return upgradeMessage(for: .batchOperations)
+ }
+ guard let limit = entitlements.batchSizeLimit, domainCount > limit else { return nil }
+ return "Batch limit is \(limit) domains on \(currentTier.title)."
+ }
+
+ static func enabledFeatureLabels() -> [String] {
+ FeatureCapability.allCases
+ .filter { hasAccess(to: $0) }
+ .map(\.title)
+ }
+}