1use serde_json::Value;
2
3use crate::client::DnaClient;
4use crate::error::DnaResult;
5use crate::models::contact::{ContactInput, ContactPayload};
6use crate::models::domain::{
7 DomainInfo, DomainInfoResponse, DomainList, DomainListItem, DomainSummary, LockPayload,
8 PrivacyPayload, RegisterPayload, RenewPayload, RenewResponse, RenewResult,
9};
10use crate::ops::util::{build_contact_payload, parse_domain_info};
11use std::collections::HashMap;
12
13const DEFAULT_NS: &[&str] = &["ns1.domainnameapi.com", "ns2.domainnameapi.com"];
15
16impl DnaClient {
17 pub async fn get_list(&self, extra: Option<&[(&str, &str)]>) -> DnaResult<DomainList> {
24 let mut params: Vec<(&str, String)> =
25 vec![("MaxResultCount", "200".into()), ("SkipCount", "0".into())];
26 if let Some(overrides) = extra {
27 for (k, v) in overrides {
28 params.retain(|(pk, _)| pk != k);
29 params.push((k, v.to_string()));
30 }
31 }
32
33 let query: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
34 let raw: Value = self.http.get("domains", Some(&query)).await?;
35
36 let total_count = raw.get("totalCount").and_then(Value::as_u64).unwrap_or(0);
37
38 let items: Vec<DomainListItem> = raw
39 .get("items")
40 .and_then(Value::as_array)
41 .map(|arr| serde_json::from_value(Value::Array(arr.clone())))
42 .transpose()?
43 .unwrap_or_default();
44
45 let domains = items
46 .into_iter()
47 .map(|item| DomainSummary {
48 id: item.id.unwrap_or(0),
49 status: item
50 .status_text
51 .or_else(|| {
52 item.status
53 .as_ref()
54 .and_then(Value::as_str)
55 .map(str::to_owned)
56 })
57 .unwrap_or_default(),
58 domain_name: item.domain_name.unwrap_or_default(),
59 auth_code: item.auth_code.unwrap_or_default(),
60 lock_status: item.lock_status.unwrap_or(false),
61 privacy_protection_status: item.privacy_protection_status.unwrap_or(false),
62 is_child_name_server: item.hosts.as_ref().map(|h| !h.is_empty()).unwrap_or(false),
63 name_servers: item.name_servers.unwrap_or_default(),
64 start_date: item.start_date.unwrap_or_default(),
65 expiration_date: item.expiration_date.unwrap_or_default(),
66 remaining_days: item.remaining_day.unwrap_or(0),
67 })
68 .collect();
69
70 Ok(DomainList {
71 domains,
72 total_count,
73 })
74 }
75
76 pub async fn get_details(&self, domain_name: &str) -> DnaResult<DomainInfo> {
80 let query = [("DomainName", domain_name)];
81 let raw: DomainInfoResponse = self.http.get("domains/info", Some(&query)).await?;
82 parse_domain_info(raw)
83 }
84
85 pub async fn sync_from_registry(&self, domain_name: &str) -> DnaResult<DomainInfo> {
87 self.get_details(domain_name).await
88 }
89
90 pub async fn renew(&self, domain_name: &str, period: u32) -> DnaResult<RenewResult> {
94 let payload = RenewPayload {
95 domain_name: domain_name.into(),
96 period,
97 };
98 let resp: RenewResponse = self.http.post("domains/renew", &payload).await?;
99
100 let expiration_date = resp.expiration_date.ok_or_else(|| {
101 crate::error::DnaError::UnexpectedResponse(
102 "Renew response missing `expirationDate`".into(),
103 )
104 })?;
105
106 Ok(RenewResult { expiration_date })
107 }
108
109 pub async fn register_with_contact_info(
113 &self,
114 domain_name: &str,
115 period: u32,
116 contacts: HashMap<&str, ContactInput>,
117 name_servers: Option<Vec<String>>,
118 epp_lock: bool,
119 privacy_lock: bool,
120 additional_attributes: Option<Value>,
121 ) -> DnaResult<DomainInfo> {
122 let payload_contacts: Vec<ContactPayload> = contacts
123 .iter()
124 .map(|(t, c)| build_contact_payload(c, t))
125 .collect();
126
127 let ns = name_servers.unwrap_or_else(|| DEFAULT_NS.iter().map(|s| s.to_string()).collect());
128
129 let payload = RegisterPayload {
130 domain_name: domain_name.into(),
131 period,
132 name_servers: ns,
133 is_locked: epp_lock,
134 privacy_enabled: privacy_lock,
135 contacts: payload_contacts,
136 additional_attributes: additional_attributes
137 .unwrap_or_else(|| Value::Object(Default::default())),
138 };
139
140 let raw: DomainInfoResponse = self
141 .http
142 .post("domains/register-with-contacts", &payload)
143 .await?;
144 parse_domain_info(raw)
145 }
146
147 pub async fn enable_theft_protection_lock(&self, domain_name: &str) -> DnaResult<()> {
151 let payload = LockPayload {
152 domain_name: domain_name.into(),
153 lock_status: true,
154 };
155 let _: Value = self.http.post("domains/lock", &payload).await?;
156 Ok(())
157 }
158
159 pub async fn disable_theft_protection_lock(&self, domain_name: &str) -> DnaResult<()> {
161 let payload = LockPayload {
162 domain_name: domain_name.into(),
163 lock_status: false,
164 };
165 let _: Value = self.http.post("domains/lock", &payload).await?;
166 Ok(())
167 }
168
169 pub async fn modify_privacy_protection_status(
176 &self,
177 domain_name: &str,
178 status: bool,
179 _reason: Option<&str>,
180 ) -> DnaResult<bool> {
181 let payload = PrivacyPayload {
182 domain_name: domain_name.into(),
183 privacy_status: status,
184 };
185 let _: Value = self.http.post("domains/privacy", &payload).await?;
186 Ok(status)
187 }
188
189 pub fn is_tr_tld(&self, domain: &str) -> bool {
193 domain.to_lowercase().ends_with(".tr")
194 }
195
196 pub fn validate_contact(&self, mut c: ContactInput) -> ContactInput {
199 fn fill(s: &mut String, default: &str) {
200 if s.trim().is_empty() {
201 *s = default.to_owned();
202 }
203 }
204
205 let first = c.first_name.clone();
207
208 fill(&mut c.first_name, "Isimyok");
209 fill(
210 &mut c.last_name,
211 if first.is_empty() { "Isimyok" } else { &first },
212 );
213 fill(&mut c.address_line1, "Addres yok");
214 fill(&mut c.city, "ISTANBUL");
215 fill(&mut c.country, "TR");
216 fill(&mut c.zip_code, "34000");
217 fill(&mut c.phone, "5555555555");
218 fill(&mut c.phone_country_code, "90");
219
220 let digits: String = c.phone.chars().filter(|ch| ch.is_ascii_digit()).collect();
222 match digits.len() {
223 10 => {
224 c.phone_country_code = String::new();
225 c.phone = digits;
226 }
227 11 if digits.starts_with('9') => {
228 c.phone_country_code = digits[..2].to_owned();
229 c.phone = digits[2..].to_owned();
230 }
231 12 if digits.starts_with("90") => {
232 c.phone_country_code = digits[..2].to_owned();
233 c.phone = digits[2..].to_owned();
234 }
235 _ => {
236 c.phone_country_code = "90".to_owned();
237 c.phone = if digits.is_empty() {
238 "5555555555".into()
239 } else {
240 digits
241 };
242 }
243 }
244
245 fill(&mut c.phone_country_code, "90");
246 fill(&mut c.phone, "5555555555");
247 c
248 }
249}