using System.Net.Http.Json; using DashCaddy.Desktop.Models; namespace DashCaddy.Desktop.Services; public enum DnsProviderType { None, // No DNS management - use existing DNS Technitium, CoreDNS, Cloudflare, Route53, Custom } public class DnsProviderConfig { public DnsProviderType Type { get; set; } public string Name { get; set; } public string BaseUrl { get; set; } public string ApiKey { get; set; } public string ZoneId { get; set; } public Dictionary Extra { get; set; } = new(); } public class DnsClient { private readonly HttpClient _httpClient; private readonly DnsProviderConfig _config; public DnsProviderType ProviderType => _config?.Type ?? DnsProviderType.None; public DnsClient(DnsProviderConfig config) { _config = config; if (config?.Type != DnsProviderType.None) { _httpClient = new HttpClient { BaseAddress = new Uri(config.BaseUrl) }; if (!string.IsNullOrEmpty(config.ApiKey)) { _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}"); } } } public static DnsClient Create(DnsProviderType type, Dictionary settings) { if (type == DnsProviderType.None) { return null; } var config = type switch { DnsProviderType.Technitium => new DnsProviderConfig { Type = DnsProviderType.Technitium, Name = "Technitium DNS", BaseUrl = settings.GetValueOrDefault("technitium_url", "http://localhost:5380/api"), ApiKey = settings.GetValueOrDefault("technitium_api_key", ""), ZoneId = settings.GetValueOrDefault("technitium_zone", "") }, DnsProviderType.CoreDNS => new DnsProviderConfig { Type = DnsProviderType.CoreDNS, Name = "CoreDNS", BaseUrl = settings.GetValueOrDefault("coredns_api_url", "http://localhost:8080"), ApiKey = "", ZoneId = settings.GetValueOrDefault("coredns_zone", "local") }, DnsProviderType.Cloudflare => new DnsProviderConfig { Type = DnsProviderType.Cloudflare, Name = "Cloudflare", BaseUrl = "https://api.cloudflare.com/client/v4", ApiKey = settings.GetValueOrDefault("cloudflare_api_token", ""), ZoneId = settings.GetValueOrDefault("cloudflare_zone_id", "") }, DnsProviderType.Route53 => new DnsProviderConfig { Type = DnsProviderType.Route53, Name = "AWS Route 53", BaseUrl = "https://route53.amazonaws.com", ApiKey = settings.GetValueOrDefault("aws_access_key", ""), Extra = new Dictionary { ["secret_key"] = settings.GetValueOrDefault("aws_secret_key", ""), ["region"] = settings.GetValueOrDefault("aws_region", "us-east-1") } }, _ => new DnsProviderConfig { Type = DnsProviderType.Custom, Name = "Custom DNS", BaseUrl = settings.GetValueOrDefault("custom_dns_url", ""), ApiKey = settings.GetValueOrDefault("custom_dns_key", "") } }; return new DnsClient(config); } public async Task> GetRecordsAsync(string zone = null) { if (_config?.Type == DnsProviderType.None) return new List(); zone ??= _config?.ZoneId; if (string.IsNullOrEmpty(zone)) return new List(); return _config.Type switch { DnsProviderType.Technitium => await GetTechnitiumRecordsAsync(zone), DnsProviderType.CoreDNS => await GetCoreDnsRecordsAsync(zone), DnsProviderType.Cloudflare => await GetCloudflareRecordsAsync(zone), DnsProviderType.Route53 => await GetRoute53RecordsAsync(zone), _ => new List() }; } public async Task CreateRecordAsync(string zone, DnsRecord record) { if (_config?.Type == DnsProviderType.None) return record; return _config.Type switch { DnsProviderType.Technitium => await CreateTechnitiumRecordAsync(zone, record), DnsProviderType.CoreDNS => await CreateCoreDnsRecordAsync(zone, record), DnsProviderType.Cloudflare => await CreateCloudflareRecordAsync(zone, record), DnsProviderType.Route53 => await CreateRoute53RecordAsync(zone, record), _ => record }; } public async Task UpdateRecordAsync(string zone, string recordId, DnsRecord record) { if (_config?.Type == DnsProviderType.None) return record; return _config.Type switch { DnsProviderType.Technitium => await UpdateTechnitiumRecordAsync(zone, recordId, record), DnsProviderType.CoreDNS => await UpdateCoreDnsRecordAsync(zone, recordId, record), DnsProviderType.Cloudflare => await UpdateCloudflareRecordAsync(zone, recordId, record), DnsProviderType.Route53 => await UpdateRoute53RecordAsync(zone, recordId, record), _ => record }; } public async Task DeleteRecordAsync(string zone, string recordId) { if (_config?.Type == DnsProviderType.None) return; switch (_config.Type) { case DnsProviderType.Technitium: await DeleteTechnitiumRecordAsync(zone, recordId); break; case DnsProviderType.CoreDNS: await DeleteCoreDnsRecordAsync(zone, recordId); break; case DnsProviderType.Cloudflare: await DeleteCloudflareRecordAsync(zone, recordId); break; case DnsProviderType.Route53: await DeleteRoute53RecordAsync(zone, recordId); break; } } // ─── Technitium ─── private async Task> GetTechnitiumRecordsAsync(string zone) { var response = await _httpClient.GetAsync($"/zones/{zone}/records"); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); return result?.Records?.Select(r => new DnsRecord { Id = r.Id, Name = r.Name, Type = r.Type, Content = r.Value, Ttl = r.Ttl }).ToList() ?? new List(); } private async Task CreateTechnitiumRecordAsync(string zone, DnsRecord record) { var req = new TechnitiumRecordRequest { Name = record.Name, Type = record.Type, Value = record.Content, Ttl = record.Ttl }; var response = await _httpClient.PostAsJsonAsync($"/zones/{zone}/records", req); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); record.Id = result?.Record?.Id; return record; } private async Task UpdateTechnitiumRecordAsync(string zone, string recordId, DnsRecord record) { var req = new TechnitiumRecordRequest { Name = record.Name, Type = record.Type, Value = record.Content, Ttl = record.Ttl }; var response = await _httpClient.PutAsJsonAsync($"/zones/{zone}/records/{recordId}", req); response.EnsureSuccessStatusCode(); return record; } private async Task DeleteTechnitiumRecordAsync(string zone, string recordId) { var response = await _httpClient.DeleteAsync($"/zones/{zone}/records/{recordId}"); response.EnsureSuccessStatusCode(); } // ─── CoreDNS (via API wrapper) ─── private async Task> GetCoreDnsRecordsAsync(string zone) { var response = await _httpClient.GetAsync($"/api/v1/dns/zones/{zone}/records"); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync>>(); return result?.Data ?? new List(); } private async Task CreateCoreDnsRecordAsync(string zone, DnsRecord record) { var response = await _httpClient.PostAsJsonAsync($"/api/v1/dns/zones/{zone}/records", record); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync>(); return result?.Data ?? record; } private async Task UpdateCoreDnsRecordAsync(string zone, string recordId, DnsRecord record) { var response = await _httpClient.PutAsJsonAsync($"/api/v1/dns/zones/{zone}/records/{recordId}", record); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync>(); return result?.Data ?? record; } private async Task DeleteCoreDnsRecordAsync(string zone, string recordId) { var response = await _httpClient.DeleteAsync($"/api/v1/dns/zones/{zone}/records/{recordId}"); response.EnsureSuccessStatusCode(); } // ─── Cloudflare ─── private async Task> GetCloudflareRecordsAsync(string zone) { var response = await _httpClient.GetAsync($"/zones/{zone}/dns_records"); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); return result?.Result?.Select(r => new DnsRecord { Id = r.Id, Name = r.Name, Type = r.Type, Content = r.Content, Ttl = r.Ttl ?? 300, Proxied = r.Proxied ?? false }).ToList() ?? new List(); } private async Task CreateCloudflareRecordAsync(string zone, DnsRecord record) { var req = new CloudflareRecordRequest { Type = record.Type, Name = record.Name, Content = record.Content, Ttl = record.Ttl, Proxied = record.Proxied }; var response = await _httpClient.PostAsJsonAsync($"/zones/{zone}/dns_records", req); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); record.Id = result?.Result?.Id; return record; } private async Task UpdateCloudflareRecordAsync(string zone, string recordId, DnsRecord record) { var req = new CloudflareRecordRequest { Type = record.Type, Name = record.Name, Content = record.Content, Ttl = record.Ttl, Proxied = record.Proxied }; var response = await _httpClient.PutAsJsonAsync($"/zones/{zone}/dns_records/{recordId}", req); response.EnsureSuccessStatusCode(); return record; } private async Task DeleteCloudflareRecordAsync(string zone, string recordId) { var response = await _httpClient.DeleteAsync($"/zones/{zone}/dns_records/{recordId}"); response.EnsureSuccessStatusCode(); } // ─── Route 53 ─── private async Task> GetRoute53RecordsAsync(string zone) { return new List(); } private async Task CreateRoute53RecordAsync(string zone, DnsRecord record) => record; private async Task UpdateRoute53RecordAsync(string zone, string recordId, DnsRecord record) => record; private async Task DeleteRoute53RecordAsync(string zone, string recordId) { } // ─── Response DTOs ─── private class ApiResponse { public bool Success { get; set; } public T Data { get; set; } public string Error { get; set; } } private class TechnitiumZoneResponse { public List Records { get; set; } } private class TechnitiumRecord { public string Id { get; set; } public string Name { get; set; } public string Type { get; set; } public string Value { get; set; } public int Ttl { get; set; } } private class TechnitiumRecordRequest { public string Name { get; set; } public string Type { get; set; } public string Value { get; set; } public int Ttl { get; set; } } private class TechnitiumRecordResponse { public TechnitiumRecord Record { get; set; } } private class CloudflareResponse { public bool Success { get; set; } public List Result { get; set; } } private class CloudflareRecord { public string Id { get; set; } public string Name { get; set; } public string Type { get; set; } public string Content { get; set; } public int? Ttl { get; set; } public bool? Proxied { get; set; } } private class CloudflareRecordRequest { public string Type { get; set; } public string Name { get; set; } public string Content { get; set; } public int Ttl { get; set; } public bool Proxied { get; set; } } private class CloudflareSingleResponse { public bool Success { get; set; } public CloudflareRecord Result { get; set; } } }