diff options
| author | Christian Cleberg <[email protected]> | 2026-04-22 11:46:52 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-22 11:46:52 -0500 |
| commit | 37b02e5ee360d61751951b77b7b556165a3af1fc (patch) | |
| tree | c2e30f32c4e8d2fcdf7035f97e9e353f09859114 /DomainDig/FeatureAccessService.swift | |
| parent | ceac99c05f180d9008b75c9329822466ff60255b (diff) | |
| download | domain-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.swift | 160 |
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) + } +} |
