diff options
| author | Christian Cleberg <[email protected]> | 2026-04-13 22:37:22 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-13 22:37:22 -0500 |
| commit | 5dcfe0f147c7871eaf5f3a88bd9e9fded38d4633 (patch) | |
| tree | 6bcbda4f02a131d3e3b870a3904ccd42f4af2f26 /Rune/API/Models.swift | |
| parent | fcf864a15b70e4ecb5bd789b1db3116221c34394 (diff) | |
| download | rune-5dcfe0f147c7871eaf5f3a88bd9e9fded38d4633.tar.gz rune-5dcfe0f147c7871eaf5f3a88bd9e9fded38d4633.tar.bz2 rune-5dcfe0f147c7871eaf5f3a88bd9e9fded38d4633.zip | |
v1.2.0 domain management and wallet expansionv1.2.0
Complete v1.1-aligned domain flows with forwards and glue CRUD, improve API payload resilience, and add wallet transaction/payment views.
Harden error handling and UX states across domain screens, remove out-of-scope DNSSEC/renewal record flows for now, and keep auth/session behavior aligned with token-based usage.
Diffstat (limited to 'Rune/API/Models.swift')
| -rw-r--r-- | Rune/API/Models.swift | 201 |
1 files changed, 200 insertions, 1 deletions
diff --git a/Rune/API/Models.swift b/Rune/API/Models.swift index 161e8df..31f928d 100644 --- a/Rune/API/Models.swift +++ b/Rune/API/Models.swift @@ -18,8 +18,46 @@ struct TokenListResponse: Codable { let tokens: [APIToken] } -struct ForwardListResponse: Codable { +struct ForwardListResponse: Decodable { let forwards: [EmailForward] + + enum CodingKeys: String, CodingKey { + case forwards + case mailforwards + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let forwardsValue = try? container.decodeIfPresent([EmailForward].self, forKey: .forwards) + let mailForwardsValue = try? container.decodeIfPresent([EmailForward].self, forKey: .mailforwards) + forwards = forwardsValue ?? mailForwardsValue ?? [] + } +} + +struct GlueListResponse: Decodable { + let glue: [GlueRecord] + + enum CodingKeys: String, CodingKey { + case glue + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + glue = (try container.decodeIfPresent([GlueRecord].self, forKey: .glue)) ?? [] + } +} + +struct WalletTransactionListResponse: Decodable { + let transactions: [WalletTransaction] + + enum CodingKeys: String, CodingKey { + case transactions + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + transactions = (try container.decodeIfPresent([WalletTransaction].self, forKey: .transactions)) ?? [] + } } struct Domain: Decodable, Identifiable, Hashable { @@ -28,6 +66,7 @@ struct Domain: Decodable, Identifiable, Hashable { let name: String let status: String? let expiry: String? + let renewPrice: Int? let autorenew: Bool? let mailforwarding: Bool? let dnssec: Bool? @@ -38,6 +77,9 @@ struct Domain: Decodable, Identifiable, Hashable { case name case status case expiry + case renewPrice = "renew_price" + case renewalPrice = "renewal_price" + case price case autorenew case mailforwarding case dnssec @@ -52,6 +94,10 @@ struct Domain: Decodable, Identifiable, Hashable { name = try container.decode(String.self, forKey: .name) status = try container.decodeIfPresent(String.self, forKey: .status) expiry = try container.decodeIfPresent(String.self, forKey: .expiry) + renewPrice = + container.decodeLossyIntIfPresent(forKey: .renewPrice) ?? + container.decodeLossyIntIfPresent(forKey: .renewalPrice) ?? + container.decodeLossyIntIfPresent(forKey: .price) autorenew = try container.decodeIfPresent(Bool.self, forKey: .autorenew) mailforwarding = try container.decodeIfPresent(Bool.self, forKey: .mailforwarding) dnssec = try container.decodeIfPresent(Bool.self, forKey: .dnssec) ?? @@ -62,6 +108,20 @@ struct Domain: Decodable, Identifiable, Hashable { } } +private extension KeyedDecodingContainer where K == Domain.CodingKeys { + func decodeLossyIntIfPresent(forKey key: K) -> Int? { + if let intValue = try? decodeIfPresent(Int.self, forKey: key) { + return intValue + } + + if let stringValue = try? decodeIfPresent(String.self, forKey: key) { + return Int(stringValue) + } + + return nil + } +} + struct DNSRecord: Codable, Identifiable, Hashable { let id: String let domain: String @@ -183,11 +243,121 @@ struct EmailForward: Codable, Hashable, Identifiable { let from: String let to: String + enum CodingKeys: String, CodingKey { + case domain + case from + case to + } + + init(domain: String, from: String, to: String) { + self.domain = domain + self.from = from + self.to = to + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let rawFrom = try container.decode(String.self, forKey: .from).trimmingCharacters(in: .whitespacesAndNewlines) + let to = try container.decode(String.self, forKey: .to).trimmingCharacters(in: .whitespacesAndNewlines) + + let decodedDomain = try container.decodeIfPresent(String.self, forKey: .domain)? + .trimmingCharacters(in: .whitespacesAndNewlines) + + if let atIndex = rawFrom.lastIndex(of: "@") { + let localPart = String(rawFrom[..<atIndex]) + let fromDomain = String(rawFrom[rawFrom.index(after: atIndex)...]) + self.from = localPart.isEmpty ? rawFrom : localPart + if let decodedDomain, !decodedDomain.isEmpty { + self.domain = decodedDomain + } else { + self.domain = fromDomain + } + } else { + self.from = rawFrom + self.domain = decodedDomain ?? "" + } + + self.to = to + } + var id: String { "\(domain)|\(from)|\(to)" } } +struct GlueRecord: Codable, Hashable, Identifiable { + let domain: String + let name: String + let address4: String? + let address6: String? + + enum CodingKeys: String, CodingKey { + case domain + case name + case address4 + case address6 + } + + init(domain: String, name: String, address4: String?, address6: String?) { + self.domain = domain + self.name = name + self.address4 = address4 + self.address6 = address6 + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + domain = (try container.decodeIfPresent(String.self, forKey: .domain)) ?? "" + name = try container.decode(String.self, forKey: .name) + address4 = try container.decodeIfPresent(String.self, forKey: .address4) + address6 = try container.decodeIfPresent(String.self, forKey: .address6) + } + + var id: String { + "\(domain)|\(name)" + } + + func withDomain(_ domain: String) -> GlueRecord { + GlueRecord(domain: domain, name: name, address4: address4, address6: address6) + } +} + +struct WalletTransaction: Codable, Hashable, Identifiable { + let id: String + let type: String? + let status: String? + let amount: Int? + let date: String? + let details: String? + + enum CodingKeys: String, CodingKey { + case id + case type + case status + case amount + case date + case details = "description" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = (try? container.decodeLossyString(forKey: .id)) ?? UUID().uuidString + type = try container.decodeIfPresent(String.self, forKey: .type) + status = try container.decodeIfPresent(String.self, forKey: .status) + amount = try container.decodeLossyIntIfPresent(forKey: .amount) + date = try container.decodeIfPresent(String.self, forKey: .date) + details = try container.decodeIfPresent(String.self, forKey: .details) + } +} + +struct WalletPayment: Codable, Hashable { + let id: String? + let amount: Int? + let status: String? + let address: String? + let url: String? +} + enum DNSRecordType: String, CaseIterable, Identifiable, Codable { case a = "A" case aaaa = "AAAA" @@ -546,3 +716,32 @@ private extension KeyedDecodingContainer where K == DNSRecord.CodingKeys { return nil } } + +private extension KeyedDecodingContainer where K == WalletTransaction.CodingKeys { + func decodeLossyString(forKey key: K) throws -> String { + if let stringValue = try decodeIfPresent(String.self, forKey: key) { + return stringValue + } + + if let intValue = try decodeIfPresent(Int.self, forKey: key) { + return String(intValue) + } + + throw DecodingError.keyNotFound( + key, + DecodingError.Context(codingPath: codingPath, debugDescription: "Missing required value for \(key.stringValue).") + ) + } + + func decodeLossyIntIfPresent(forKey key: K) throws -> Int? { + if let intValue = try decodeIfPresent(Int.self, forKey: key) { + return intValue + } + + if let stringValue = try decodeIfPresent(String.self, forKey: key) { + return Int(stringValue) + } + + return nil + } +} |
