feat: Add the updateDnsRecords to DNS providers

This commit is contained in:
Inex Code 2024-07-18 18:26:31 +04:00
parent 95e51732fc
commit eab2f892c9
10 changed files with 226 additions and 7 deletions

View file

@ -155,7 +155,7 @@ class DesecApi extends RestApiMap {
return GenericResult(success: true, data: null); return GenericResult(success: true, data: null);
} }
Future<GenericResult<void>> removeSimilarRecords({ Future<GenericResult<void>> putRecords({
required final String domainName, required final String domainName,
required final List<DesecDnsRecord> records, required final List<DesecDnsRecord> records,
}) async { }) async {

View file

@ -43,6 +43,7 @@ class CloudflareDnsRecord {
this.ttl = 3600, this.ttl = 3600,
this.priority = 10, this.priority = 10,
this.id, this.id,
this.comment = 'Created by SelfPrivacy app',
}); });
factory CloudflareDnsRecord.fromDnsRecord( factory CloudflareDnsRecord.fromDnsRecord(
@ -90,6 +91,9 @@ class CloudflareDnsRecord {
/// `>= 0 <= 65535` /// `>= 0 <= 65535`
final int priority; final int priority;
/// Comments or notes about the DNS record. This field has no effect on DNS responses.
final String comment;
static CloudflareDnsRecord fromJson(final Map<String, dynamic> json) => static CloudflareDnsRecord fromJson(final Map<String, dynamic> json) =>
_$CloudflareDnsRecordFromJson(json); _$CloudflareDnsRecordFromJson(json);
Map<String, dynamic> toJson() => _$CloudflareDnsRecordToJson(this); Map<String, dynamic> toJson() => _$CloudflareDnsRecordToJson(this);

View file

@ -27,6 +27,7 @@ CloudflareDnsRecord _$CloudflareDnsRecordFromJson(Map<String, dynamic> json) =>
ttl: (json['ttl'] as num?)?.toInt() ?? 3600, ttl: (json['ttl'] as num?)?.toInt() ?? 3600,
priority: (json['priority'] as num?)?.toInt() ?? 10, priority: (json['priority'] as num?)?.toInt() ?? 10,
id: json['id'] as String?, id: json['id'] as String?,
comment: json['comment'] as String? ?? 'Created by SelfPrivacy app',
); );
Map<String, dynamic> _$CloudflareDnsRecordToJson( Map<String, dynamic> _$CloudflareDnsRecordToJson(
@ -39,4 +40,5 @@ Map<String, dynamic> _$CloudflareDnsRecordToJson(
'zone_name': instance.zoneName, 'zone_name': instance.zoneName,
'ttl': instance.ttl, 'ttl': instance.ttl,
'priority': instance.priority, 'priority': instance.priority,
'comment': instance.comment,
}; };

View file

@ -1,11 +1,12 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
part 'dns_records.g.dart'; part 'dns_records.g.dart';
@JsonSerializable(createToJson: true, createFactory: false) @JsonSerializable()
class DnsRecord { class DnsRecord extends Equatable {
DnsRecord({ const DnsRecord({
required this.type, required this.type,
required this.name, required this.name,
required this.content, required this.content,
@ -35,4 +36,14 @@ class DnsRecord {
final bool proxied; final bool proxied;
Map<String, dynamic> toJson() => _$DnsRecordToJson(this); Map<String, dynamic> toJson() => _$DnsRecordToJson(this);
@override
@JsonKey(includeToJson: false)
List<Object?> get props => [
type,
name,
content,
ttl,
priority,
];
} }

View file

@ -6,6 +6,16 @@ part of 'dns_records.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
DnsRecord _$DnsRecordFromJson(Map<String, dynamic> json) => DnsRecord(
type: json['type'] as String,
name: json['name'] as String?,
content: json['content'] as String?,
displayName: json['displayName'] as String?,
ttl: (json['ttl'] as num?)?.toInt() ?? 3600,
priority: (json['priority'] as num?)?.toInt() ?? 10,
proxied: json['proxied'] as bool? ?? false,
);
Map<String, dynamic> _$DnsRecordToJson(DnsRecord instance) => <String, dynamic>{ Map<String, dynamic> _$DnsRecordToJson(DnsRecord instance) => <String, dynamic>{
'type': instance.type, 'type': instance.type,
'displayName': instance.displayName, 'displayName': instance.displayName,

View file

@ -198,6 +198,94 @@ class CloudflareDnsProvider extends DnsProvider {
); );
} }
@override
Future<GenericResult<void>> updateDnsRecords({
required final List<DnsRecord> newRecords,
required final ServerDomain domain,
final List<DnsRecord>? oldRecords,
}) async {
final syncZoneIdResult = await syncZoneId(domain.domainName);
if (!syncZoneIdResult.success) {
return syncZoneIdResult;
}
final result = await _adapter.api().getDnsRecords(
zoneId: _adapter.cachedZoneId,
);
if (result.data.isEmpty || !result.success) {
return GenericResult(
success: false,
data: null,
code: result.code,
message: result.message,
);
}
final List<CloudflareDnsRecord> newSelfprivacyRecords = newRecords
.map(
(final record) => CloudflareDnsRecord.fromDnsRecord(
record,
domain.domainName,
),
)
.toList();
final List<CloudflareDnsRecord>? oldSelfprivacyRecords = oldRecords
?.map(
(final record) => CloudflareDnsRecord.fromDnsRecord(
record,
domain.domainName,
),
)
.toList();
final List<CloudflareDnsRecord> cloudflareRecords = result.data;
final List<CloudflareDnsRecord> recordsToDelete = newSelfprivacyRecords
.where(
(final newRecord) => cloudflareRecords.any(
(final oldRecord) =>
newRecord.type == oldRecord.type &&
newRecord.name == oldRecord.name,
),
)
.toList();
if (oldSelfprivacyRecords != null) {
recordsToDelete.addAll(
oldSelfprivacyRecords
.where(
(final oldRecord) => !newSelfprivacyRecords.any(
(final newRecord) =>
newRecord.type == oldRecord.type &&
newRecord.name == oldRecord.name,
),
)
.toList(),
);
}
if (recordsToDelete.isNotEmpty) {
await _adapter.api().removeSimilarRecords(
records: cloudflareRecords
.where(
(final record) => recordsToDelete.any(
(final recordToDelete) =>
recordToDelete.type == record.type &&
recordToDelete.name == record.name,
),
)
.toList(),
zoneId: _adapter.cachedZoneId,
);
}
return _adapter.api().createMultipleDnsRecords(
zoneId: _adapter.cachedZoneId,
records: newSelfprivacyRecords,
);
}
Future<GenericResult<void>> syncZoneId(final String domain) async { Future<GenericResult<void>> syncZoneId(final String domain) async {
if (domain == _adapter.cachedDomain && _adapter.cachedZoneId.isNotEmpty) { if (domain == _adapter.cachedDomain && _adapter.cachedZoneId.isNotEmpty) {
return GenericResult( return GenericResult(

View file

@ -112,7 +112,28 @@ class DesecDnsProvider extends DnsProvider {
); );
} }
return _adapter.api().removeSimilarRecords( return _adapter.api().putRecords(
domainName: domain.domainName,
records: bulkRecords,
);
}
@override
Future<GenericResult<void>> updateDnsRecords({
required final List<DnsRecord> newRecords,
required final ServerDomain domain,
final List<DnsRecord>? oldRecords,
}) async {
if (oldRecords != null) {
await removeDomainRecords(records: oldRecords, domain: domain);
}
final List<DesecDnsRecord> bulkRecords = [];
for (final DnsRecord record in newRecords) {
bulkRecords.add(DesecDnsRecord.fromDnsRecord(record, domain.domainName));
}
return _adapter.api().putRecords(
domainName: domain.domainName, domainName: domain.domainName,
records: bulkRecords, records: bulkRecords,
); );

View file

@ -128,6 +128,79 @@ class DigitalOceanDnsProvider extends DnsProvider {
); );
} }
@override
Future<GenericResult<void>> updateDnsRecords({
required final List<DnsRecord> newRecords,
required final ServerDomain domain,
final List<DnsRecord>? oldRecords,
}) async {
final result = await _adapter.api().getDnsRecords(domain.domainName);
if (result.data.isEmpty || !result.success) {
return GenericResult(
success: result.success,
data: null,
code: result.code,
message: result.message,
);
}
final List<DigitalOceanDnsRecord> newSelfprivacyRecords = newRecords
.map(
(final record) => DigitalOceanDnsRecord.fromDnsRecord(
record,
domain.domainName,
),
)
.toList();
final List<DigitalOceanDnsRecord>? oldSelfprivacyRecords = oldRecords
?.map(
(final record) => DigitalOceanDnsRecord.fromDnsRecord(
record,
domain.domainName,
),
)
.toList();
final List<DigitalOceanDnsRecord> oceanRecords = result.data;
final List<DigitalOceanDnsRecord> recordsToDelete = newSelfprivacyRecords
.where(
(final newRecord) => oceanRecords.any(
(final oldRecord) =>
newRecord.type == oldRecord.type &&
newRecord.name == oldRecord.name,
),
)
.toList();
if (oldSelfprivacyRecords != null) {
recordsToDelete.addAll(
oldSelfprivacyRecords
.where(
(final oldRecord) => !newSelfprivacyRecords.any(
(final newRecord) =>
newRecord.type == oldRecord.type &&
newRecord.name == oldRecord.name,
),
)
.toList(),
);
}
if (recordsToDelete.isNotEmpty) {
return _adapter.api().removeSimilarRecords(
domainName: domain.domainName,
records: recordsToDelete,
);
}
return _adapter.api().createMultipleDnsRecords(
domainName: domain.domainName,
records: newSelfprivacyRecords,
);
}
@override @override
Future<GenericResult<List<DnsRecord>>> getDnsRecords({ Future<GenericResult<List<DnsRecord>>> getDnsRecords({
required final ServerDomain domain, required final ServerDomain domain,

View file

@ -54,4 +54,14 @@ abstract class DnsProvider {
final DnsRecord record, final DnsRecord record,
final ServerDomain domain, final ServerDomain domain,
); );
/// Tries to update existing domain records
///
/// If [oldRecords] is provided, it will also remove the records that
/// are not in [newRecords
Future<GenericResult<void>> updateDnsRecords({
required final List<DnsRecord> newRecords,
required final ServerDomain domain,
final List<DnsRecord>? oldRecords,
});
} }

View file

@ -108,7 +108,7 @@ List<DnsRecord> getProjectDnsRecords(
final DnsRecord socialA = DnsRecord(type: 'A', name: 'social', content: ip4); final DnsRecord socialA = DnsRecord(type: 'A', name: 'social', content: ip4);
final DnsRecord vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4); final DnsRecord vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
final DnsRecord txt1 = DnsRecord( const DnsRecord txt1 = DnsRecord(
type: 'TXT', type: 'TXT',
name: '_dmarc', name: '_dmarc',
content: 'v=DMARC1; p=none', content: 'v=DMARC1; p=none',
@ -125,7 +125,7 @@ List<DnsRecord> getProjectDnsRecords(
/// We never create this record! /// We never create this record!
/// This declaration is only for removal /// This declaration is only for removal
/// as we need to compare by 'type' and 'name' /// as we need to compare by 'type' and 'name'
final DnsRecord txt3 = DnsRecord( const DnsRecord txt3 = DnsRecord(
type: 'TXT', type: 'TXT',
name: 'selector._domainkey', name: 'selector._domainkey',
content: 'v=DKIM1; k=rsa; p=none', content: 'v=DKIM1; k=rsa; p=none',