13 KiB
13 KiB
CommunityPool Component
The CommunityPool component manages the donation and distribution of license keys within the VoiceUwu community.
Pseudocode
class CommunityPoolManager {
// CloudKit container for community features
private let container = CKContainer(identifier: "iCloud.com.voiceuwu.community")
private let publicDB: CKDatabase
// Local cache
@Published private(set) var poolStatus: PoolStatus?
@Published private(set) var userRequests: [KeyRequest] = []
@Published private(set) var isLoading = false
init() {
self.publicDB = container.publicCloudDatabase
loadPoolStatus()
}
// MARK: - Pool Status
struct PoolStatus {
let totalKeys: Int
let level1Keys: Int
let level2Keys: Int
let recentDonations: [DonationInfo]
let lastUpdated: Date
}
func loadPoolStatus() async {
isLoading = true
defer { isLoading = false }
do {
// Query pool statistics
let query = CKQuery(
recordType: "PoolStatus",
predicate: NSPredicate(value: true)
)
let results = try await publicDB.records(matching: query)
if let record = results.matchResults.first?.1.get() {
poolStatus = PoolStatus(
totalKeys: record["totalKeys"] as? Int ?? 0,
level1Keys: record["level1Keys"] as? Int ?? 0,
level2Keys: record["level2Keys"] as? Int ?? 0,
recentDonations: parseRecentDonations(record),
lastUpdated: record.modificationDate ?? Date()
)
}
} catch {
print("Failed to load pool status: \(error)")
}
}
// MARK: - Donation System
struct KeyDonation {
let keys: [String]
let donorName: String?
let message: String?
let isAnonymous: Bool
}
func donateKeys(_ donation: KeyDonation) async throws {
// Validate keys before donation
for key in donation.keys {
guard KeyValidator().validate(key).isValid else {
throw DonationError.invalidKey(key)
}
}
// Create donation records
for key in donation.keys {
let record = CKRecord(recordType: "KeyDonation")
// Store encrypted key
record["encryptedKey"] = encrypt(key)
record["keyLevel"] = extractLevel(from: key).rawValue
record["donationDate"] = Date()
// Donor info (anonymous if requested)
if !donation.isAnonymous {
record["donorName"] = donation.donorName ?? "Unknown"
}
record["donorHash"] = generateDonorHash()
// Optional message
if let message = donation.message {
record["message"] = sanitizeMessage(message)
}
try await publicDB.save(record)
}
// Remove keys from local inventory
KeyManager.shared.removeFromInventory(donation.keys)
// Update local cache
await loadPoolStatus()
}
// MARK: - Request System
struct KeyRequest {
let id: String
let category: RequestCategory
let reason: String
let requestDate: Date
let status: RequestStatus
let level: LicenseLevel
}
enum RequestCategory: String, CaseIterable {
case student = "student"
case educator = "educator"
case financial = "financial"
case contributor = "contributor"
var displayName: String {
switch self {
case .student: return "Student"
case .educator: return "Educator"
case .financial: return "Financial Need"
case .contributor: return "Open Source Contributor"
}
}
var verificationRequired: Bool {
switch self {
case .student, .educator: return true
case .financial, .contributor: return false
}
}
}
enum RequestStatus: String {
case pending = "pending"
case approved = "approved"
case denied = "denied"
case fulfilled = "fulfilled"
}
func submitRequest(
category: RequestCategory,
reason: String,
level: LicenseLevel,
verification: VerificationData?
) async throws {
// Check request limits
let existingRequests = try await loadUserRequests()
// One request per 30 days
if let lastRequest = existingRequests.last {
let daysSinceLastRequest = Date().timeIntervalSince(lastRequest.requestDate) / 86400
if daysSinceLastRequest < 30 {
throw RequestError.tooSoon(daysRemaining: Int(30 - daysSinceLastRequest))
}
}
// Create request record
let record = CKRecord(recordType: "KeyRequest")
record["deviceID"] = getAnonymousDeviceID()
record["category"] = category.rawValue
record["reason"] = sanitizeReason(reason)
record["requestDate"] = Date()
record["status"] = RequestStatus.pending.rawValue
record["level"] = level.rawValue
// Add verification if required
if category.verificationRequired {
guard let verification = verification else {
throw RequestError.verificationRequired
}
record["verification"] = try encodeVerification(verification)
}
try await publicDB.save(record)
// Reload requests
userRequests = try await loadUserRequests()
}
// MARK: - Distribution Algorithm
func checkForApprovedKeys() async throws -> String? {
let deviceID = getAnonymousDeviceID()
// Query for approved requests
let predicate = NSPredicate(
format: "deviceID == %@ AND status == %@",
deviceID,
RequestStatus.approved.rawValue
)
let query = CKQuery(recordType: "KeyRequest", predicate: predicate)
let results = try await publicDB.records(matching: query)
for (_, result) in results.matchResults {
if case .success(let record) = result,
let keyID = record["assignedKeyID"] as? String {
// Fetch the actual key
let key = try await fetchAssignedKey(keyID)
// Mark as fulfilled
record["status"] = RequestStatus.fulfilled.rawValue
try await publicDB.save(record)
return key
}
}
return nil
}
private func fetchAssignedKey(_ keyID: String) async throws -> String {
// Fetch encrypted key from secure storage
let recordID = CKRecord.ID(recordName: keyID)
let record = try await publicDB.record(for: recordID)
guard let encryptedKey = record["encryptedKey"] as? String else {
throw DistributionError.keyNotFound
}
return decrypt(encryptedKey)
}
// MARK: - Privacy & Security
private func getAnonymousDeviceID() -> String {
// Generate consistent anonymous ID
if let existing = UserDefaults.standard.string(forKey: "anonymousDeviceID") {
return existing
}
let newID = UUID().uuidString
UserDefaults.standard.set(newID, forKey: "anonymousDeviceID")
return newID
}
private func generateDonorHash() -> String {
// One-way hash for donor tracking without identification
let deviceID = getAnonymousDeviceID()
let timestamp = Date().timeIntervalSince1970
let combined = "\(deviceID)\(timestamp)"
return SHA256.hash(data: Data(combined.utf8))
.compactMap { String(format: "%02x", $0) }
.joined()
.prefix(8)
.uppercased()
}
private func encrypt(_ key: String) -> String {
// Simplified encryption for demonstration
// In production, use proper encryption
return Data(key.utf8).base64EncodedString()
}
private func decrypt(_ encrypted: String) -> String {
// Simplified decryption for demonstration
guard let data = Data(base64Encoded: encrypted),
let key = String(data: data, encoding: .utf8) else {
return ""
}
return key
}
private func sanitizeMessage(_ message: String) -> String {
// Remove any potentially harmful content
let cleaned = message
.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: "[^a-zA-Z0-9 .,!?'-]", with: "", options: .regularExpression)
return String(cleaned.prefix(200)) // Limit length
}
}
// MARK: - UI Components
struct CommunityPoolView: View {
@StateObject private var poolManager = CommunityPoolManager()
@State private var selectedTab = 0
var body: some View {
VStack(spacing: 0) {
// Pool status header
if let status = poolManager.poolStatus {
PoolStatusHeader(status: status)
}
// Tab selection
Picker("View", selection: $selectedTab) {
Text("Donate").tag(0)
Text("Request").tag(1)
Text("Activity").tag(2)
}
.pickerStyle(SegmentedPickerStyle())
.padding()
// Content
ScrollView {
switch selectedTab {
case 0:
DonateKeysView()
case 1:
RequestKeyView()
case 2:
CommunityActivityView()
default:
EmptyView()
}
}
}
.navigationTitle("Community Pool")
.task {
await poolManager.loadPoolStatus()
}
}
}
struct PoolStatusHeader: View {
let status: CommunityPoolManager.PoolStatus
var body: some View {
VStack(spacing: 12) {
Text("Community Pool")
.font(.headline)
HStack(spacing: 20) {
VStack {
Text("\(status.totalKeys)")
.font(.title2)
.fontWeight(.bold)
Text("Total Keys")
.font(.caption)
.foregroundColor(.secondary)
}
Divider()
.frame(height: 40)
VStack {
Text("\(status.level1Keys)")
.font(.title3)
.foregroundColor(.blue)
Text("Level 1")
.font(.caption)
.foregroundColor(.secondary)
}
VStack {
Text("\(status.level2Keys)")
.font(.title3)
.foregroundColor(.purple)
Text("Level 2")
.font(.caption)
.foregroundColor(.secondary)
}
}
if !status.recentDonations.isEmpty {
Text("Recent: \(status.recentDonations.first!.message)")
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
}
}
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(12)
.padding()
}
}
// MARK: - Error Types
enum DonationError: LocalizedError {
case invalidKey(String)
case donationFailed
var errorDescription: String? {
switch self {
case .invalidKey(let key):
return "Invalid key: \(key)"
case .donationFailed:
return "Failed to donate keys"
}
}
}
enum RequestError: LocalizedError {
case tooSoon(daysRemaining: Int)
case verificationRequired
case requestFailed
var errorDescription: String? {
switch self {
case .tooSoon(let days):
return "Please wait \(days) more days before requesting again"
case .verificationRequired:
return "Verification required for this category"
case .requestFailed:
return "Failed to submit request"
}
}
}
Community Pool Features
-
Anonymous Donations
- No user tracking
- Optional messages
- Donor recognition (optional)
-
Fair Distribution
- Category-based priority
- Request limits (30-day cooldown)
- Verification for some categories
-
Privacy First
- Anonymous device IDs
- Encrypted key storage
- No personal data collection
-
Community Building
- Recent donation feed
- Pool statistics
- Success stories