From 146350f8f41b209802b10c42d353f0bf588eb539 Mon Sep 17 00:00:00 2001
From: NaiJi <naijiworld@protonmail.com>
Date: Wed, 26 Jul 2023 21:23:37 -0300
Subject: [PATCH] refactor(desec): Implement basic DTO for DESEC to avoid
 dynamic objects

---
 .../dns_providers/desec/desec_api.dart        | 47 +++++++-----
 lib/logic/models/json/desec_dns_info.dart     | 63 ++++++++++++++++
 lib/logic/models/json/desec_dns_info.g.dart   | 35 +++++++++
 lib/logic/models/json/dns_records.g.dart      |  1 -
 lib/logic/providers/dns_providers/desec.dart  | 74 +++++++++----------
 5 files changed, 164 insertions(+), 56 deletions(-)
 create mode 100644 lib/logic/models/json/desec_dns_info.dart
 create mode 100644 lib/logic/models/json/desec_dns_info.g.dart

diff --git a/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_api.dart b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_api.dart
index 960df30f..9856242d 100644
--- a/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_api.dart
+++ b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_api.dart
@@ -4,7 +4,7 @@ import 'package:dio/dio.dart';
 import 'package:selfprivacy/config/get_it_config.dart';
 import 'package:selfprivacy/logic/api_maps/generic_result.dart';
 import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
-import 'package:selfprivacy/logic/models/hive/server_domain.dart';
+import 'package:selfprivacy/logic/models/json/desec_dns_info.dart';
 
 class DesecApi extends RestApiMap {
   DesecApi({
@@ -92,8 +92,8 @@ class DesecApi extends RestApiMap {
     );
   }
 
-  Future<GenericResult<List>> getDomains() async {
-    List domains = [];
+  Future<GenericResult<List<DesecDomain>>> getDomains() async {
+    List<DesecDomain> domains = [];
 
     late final Response? response;
     final Dio client = await getClient();
@@ -102,7 +102,11 @@ class DesecApi extends RestApiMap {
         '',
       );
       await Future.delayed(const Duration(seconds: 1));
-      domains = response.data;
+      domains = response.data!
+          .map<DesecDomain>(
+            (final e) => DesecDomain.fromJson(e),
+          )
+          .toList();
     } catch (e) {
       print(e);
       return GenericResult(
@@ -124,15 +128,17 @@ class DesecApi extends RestApiMap {
   }
 
   Future<GenericResult<void>> createMultipleDnsRecords({
-    required final ServerDomain domain,
-    required final List<dynamic> records,
+    required final String domainName,
+    required final List<DesecDnsRecord> records,
   }) async {
-    final String domainName = domain.domainName;
     final String url = '/$domainName/rrsets/';
 
     final Dio client = await getClient();
     try {
-      await client.post(url, data: records);
+      await client.post(
+        url,
+        data: records.map((final rec) => rec.toJson()).toList(),
+      );
       await Future.delayed(const Duration(seconds: 1));
     } catch (e) {
       print(e);
@@ -149,15 +155,17 @@ class DesecApi extends RestApiMap {
   }
 
   Future<GenericResult<void>> removeSimilarRecords({
-    required final ServerDomain domain,
-    required final List<dynamic> records,
+    required final String domainName,
+    required final List<DesecDnsRecord> records,
   }) async {
-    final String domainName = domain.domainName;
     final String url = '/$domainName/rrsets/';
 
     final Dio client = await getClient();
     try {
-      await client.put(url, data: records);
+      await client.put(
+        url,
+        data: records.map((final rec) => rec.toJson()).toList(),
+      );
       await Future.delayed(const Duration(seconds: 1));
     } catch (e) {
       print(e);
@@ -173,12 +181,11 @@ class DesecApi extends RestApiMap {
     return GenericResult(success: true, data: null);
   }
 
-  Future<GenericResult<List<dynamic>>> getDnsRecords({
-    required final ServerDomain domain,
-  }) async {
+  Future<GenericResult<List<DesecDnsRecord>>> getDnsRecords(
+    final String domainName,
+  ) async {
     Response? response;
-    final String domainName = domain.domainName;
-    List allRecords = [];
+    List<DesecDnsRecord> allRecords = [];
 
     final String url = '/$domainName/rrsets/';
 
@@ -186,7 +193,11 @@ class DesecApi extends RestApiMap {
     try {
       response = await client.get(url);
       await Future.delayed(const Duration(seconds: 1));
-      allRecords = response.data;
+      allRecords = response.data!
+          .map<DesecDnsRecord>(
+            (final e) => DesecDnsRecord.fromJson(e),
+          )
+          .toList();
     } catch (e) {
       print(e);
       return GenericResult(
diff --git a/lib/logic/models/json/desec_dns_info.dart b/lib/logic/models/json/desec_dns_info.dart
new file mode 100644
index 00000000..e6023da7
--- /dev/null
+++ b/lib/logic/models/json/desec_dns_info.dart
@@ -0,0 +1,63 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'desec_dns_info.g.dart';
+
+/// https://desec.readthedocs.io/en/latest/dns/domains.html#domain-management
+@JsonSerializable()
+class DesecDomain {
+  DesecDomain(
+    this.name,
+    this.minimumTtl,
+  );
+
+  /// Restrictions on what is a valid domain name apply on
+  /// a per-user basis.
+  ///
+  /// The maximum length is 191.
+  final String name;
+
+  /// Smallest TTL that can be used in an RRset.
+  /// The value is set automatically by DESEC
+  @JsonKey(name: 'minimum_ttl')
+  final int minimumTtl;
+
+  static DesecDomain fromJson(final Map<String, dynamic> json) =>
+      _$DesecDomainFromJson(json);
+}
+
+/// https://desec.readthedocs.io/en/latest/dns/rrsets.html#retrieving-and-creating-dns-records
+@JsonSerializable()
+class DesecDnsRecord {
+  DesecDnsRecord({
+    required this.subname,
+    required this.type,
+    required this.ttl,
+    required this.records,
+  });
+
+  /// Subdomain string which, together with domain, defines the RRset name.
+  /// Typical examples are www or _443._tcp.
+  final String subname;
+
+  /// RRset type (uppercase). A broad range of record types is supported,
+  /// with most DNSSEC-related types (and the SOA type) managed automagically
+  /// by the backend.
+  final String type;
+
+  /// Time-to-live value, which dictates for how long resolvers may
+  /// cache this RRset, measured in seconds.
+  ///
+  /// The smallest acceptable value is given by the domain’s minimum TTL setting.
+  /// The maximum value is 86400 (one day).
+  final int ttl;
+
+  /// Array of record content strings.
+  ///
+  /// The maximum number of array elements is 4091,
+  /// and the maximum length of the array is 64,000 (after JSON encoding).
+  final List<String> records;
+
+  static DesecDnsRecord fromJson(final Map<String, dynamic> json) =>
+      _$DesecDnsRecordFromJson(json);
+  Map<String, dynamic> toJson() => _$DesecDnsRecordToJson(this);
+}
diff --git a/lib/logic/models/json/desec_dns_info.g.dart b/lib/logic/models/json/desec_dns_info.g.dart
new file mode 100644
index 00000000..b82ed777
--- /dev/null
+++ b/lib/logic/models/json/desec_dns_info.g.dart
@@ -0,0 +1,35 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'desec_dns_info.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+DesecDomain _$DesecDomainFromJson(Map<String, dynamic> json) => DesecDomain(
+      json['name'] as String,
+      json['minimum_ttl'] as int,
+    );
+
+Map<String, dynamic> _$DesecDomainToJson(DesecDomain instance) =>
+    <String, dynamic>{
+      'name': instance.name,
+      'minimum_ttl': instance.minimumTtl,
+    };
+
+DesecDnsRecord _$DesecDnsRecordFromJson(Map<String, dynamic> json) =>
+    DesecDnsRecord(
+      subname: json['subname'] as String,
+      type: json['type'] as String,
+      ttl: json['ttl'] as int,
+      records:
+          (json['records'] as List<dynamic>).map((e) => e as String).toList(),
+    );
+
+Map<String, dynamic> _$DesecDnsRecordToJson(DesecDnsRecord instance) =>
+    <String, dynamic>{
+      'subname': instance.subname,
+      'type': instance.type,
+      'ttl': instance.ttl,
+      'records': instance.records,
+    };
diff --git a/lib/logic/models/json/dns_records.g.dart b/lib/logic/models/json/dns_records.g.dart
index b58db5de..c8c12c34 100644
--- a/lib/logic/models/json/dns_records.g.dart
+++ b/lib/logic/models/json/dns_records.g.dart
@@ -13,5 +13,4 @@ Map<String, dynamic> _$DnsRecordToJson(DnsRecord instance) => <String, dynamic>{
       'ttl': instance.ttl,
       'priority': instance.priority,
       'proxied': instance.proxied,
-      'id': instance.id,
     };
diff --git a/lib/logic/providers/dns_providers/desec.dart b/lib/logic/providers/dns_providers/desec.dart
index 41d504e4..e20d82bf 100644
--- a/lib/logic/providers/dns_providers/desec.dart
+++ b/lib/logic/providers/dns_providers/desec.dart
@@ -1,6 +1,7 @@
 import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desec/desec_api.dart';
 import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
 import 'package:selfprivacy/logic/models/hive/server_domain.dart';
+import 'package:selfprivacy/logic/models/json/desec_dns_info.dart';
 import 'package:selfprivacy/logic/models/json/dns_records.dart';
 import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
 
@@ -59,7 +60,7 @@ class DesecDnsProvider extends DnsProvider {
 
     domains = result.data
         .map<String>(
-          (final el) => el['name'] as String,
+          (final el) => el.name,
         )
         .toList();
 
@@ -79,20 +80,20 @@ class DesecDnsProvider extends DnsProvider {
       ip4,
     );
 
-    final List<dynamic> bulkRecords = [];
+    final List<DesecDnsRecord> bulkRecords = [];
     for (final DnsRecord record in listDnsRecords) {
       bulkRecords.add(
-        {
-          'subname': record.name,
-          'type': record.type,
-          'ttl': record.ttl,
-          'records': [extractContent(record)],
-        },
+        DesecDnsRecord(
+          subname: record.name ?? '',
+          type: record.type,
+          ttl: record.ttl,
+          records: [extractContent(record) ?? ''],
+        ),
       );
     }
 
     return _adapter.api().createMultipleDnsRecords(
-          domain: domain,
+          domainName: domain.domainName,
           records: bulkRecords,
         );
   }
@@ -107,28 +108,28 @@ class DesecDnsProvider extends DnsProvider {
       ip4,
     );
 
-    final List<dynamic> bulkRecords = [];
+    final List<DesecDnsRecord> bulkRecords = [];
     for (final DnsRecord record in listDnsRecords) {
       bulkRecords.add(
-        {
-          'subname': record.name,
-          'type': record.type,
-          'ttl': record.ttl,
-          'records': [],
-        },
+        DesecDnsRecord(
+          subname: record.name ?? '',
+          type: record.type,
+          ttl: record.ttl,
+          records: [],
+        ),
       );
     }
     bulkRecords.add(
-      {
-        'subname': 'selector._domainkey',
-        'type': 'TXT',
-        'ttl': 18000,
-        'records': [],
-      },
+      DesecDnsRecord(
+        subname: 'selector._domainkey',
+        type: 'TXT',
+        ttl: 18000,
+        records: [],
+      ),
     );
 
     return _adapter.api().removeSimilarRecords(
-          domain: domain,
+          domainName: domain.domainName,
           records: bulkRecords,
         );
   }
@@ -138,7 +139,7 @@ class DesecDnsProvider extends DnsProvider {
     required final ServerDomain domain,
   }) async {
     final List<DnsRecord> records = [];
-    final result = await _adapter.api().getDnsRecords(domain: domain);
+    final result = await _adapter.api().getDnsRecords(domain.domainName);
     if (result.data.isEmpty || !result.success) {
       return GenericResult(
         success: result.success,
@@ -150,15 +151,14 @@ class DesecDnsProvider extends DnsProvider {
 
     try {
       for (final record in result.data) {
-        final String? content = (record['records'] is List<dynamic>)
-            ? record['records'][0]
-            : record['records'];
+        final String? content =
+            record.records.isEmpty ? null : record.records[0];
         records.add(
           DnsRecord(
-            name: record['subname'],
-            type: record['type'],
+            name: record.subname,
+            type: record.type,
             content: content,
-            ttl: record['ttl'],
+            ttl: record.ttl,
           ),
         );
       }
@@ -180,14 +180,14 @@ class DesecDnsProvider extends DnsProvider {
     final ServerDomain domain,
   ) async {
     final result = await _adapter.api().createMultipleDnsRecords(
-      domain: domain,
+      domainName: domain.domainName,
       records: [
-        {
-          'subname': record.name,
-          'type': record.type,
-          'ttl': record.ttl,
-          'records': [extractContent(record)],
-        },
+        DesecDnsRecord(
+          subname: record.name ?? '',
+          type: record.type,
+          ttl: record.ttl,
+          records: [extractContent(record) ?? ''],
+        ),
       ],
     );