|
2 | 2 | from pydantic import BaseModel |
3 | 3 | from typing import Optional |
4 | 4 | from app.services.cache import WhoisCache |
5 | | -from app.services.whois import WhoisService |
| 5 | +from app.services.whois import WhoisService, parse_whois |
6 | 6 | from app.services.rate_limiter import RateLimiter |
7 | 7 | import logging |
8 | 8 |
|
@@ -46,80 +46,33 @@ async def get_whois( |
46 | 46 | tld = parts[-1] |
47 | 47 |
|
48 | 48 | # 2. Cache |
49 | | - def parse_whois(raw: str, tld: str): |
50 | | - """Extract statut, creation_date, registrar, pendingDelete, redemptionPeriod for all TLDs. |
51 | | -
|
52 | | - This is heuristic: we search common WHOIS labels case-insensitively. |
53 | | - Returns a dict with keys 'statut', 'creation_date', 'registrar', 'pendingDelete', 'redemptionPeriod'. |
54 | | - """ |
55 | | - if not raw: |
56 | | - return { |
57 | | - "statut": None, |
58 | | - "creation_date": None, |
59 | | - "registrar": None, |
60 | | - "pendingDelete": False, |
61 | | - "redemptionPeriod": False, |
62 | | - } |
63 | | - |
64 | | - raw_lines = [l.strip() for l in raw.splitlines() if l.strip()] |
65 | | - lower = raw.lower() |
66 | | - |
67 | | - statut = None |
68 | | - creation_date = None |
69 | | - registrar = None |
70 | | - pendingDelete = False |
71 | | - redemptionPeriod = False |
72 | | - |
73 | | - import re |
74 | | - |
75 | | - # Common patterns (now generalized for all TLDs) |
76 | | - for line in raw_lines: |
77 | | - l = line.lower() |
78 | | - # Registrar: (ignore Registrar WHOIS Server and Registrar URL) |
79 | | - if registrar is None and l.startswith("registrar:") and not ("whois server" in l or "url" in l): |
80 | | - parts = line.split(":", 1) |
81 | | - if len(parts) == 2: |
82 | | - registrar = parts[1].strip() |
83 | | - continue |
84 | | - # Creation date |
85 | | - if creation_date is None and ("creation date" in l or "created on" in l or "created:" in l or "creation:" in l or "registered on" in l): |
86 | | - parts = line.split(":", 1) |
87 | | - if len(parts) == 2: |
88 | | - creation_date = parts[1].strip() |
89 | | - continue |
90 | | - # Status lines (can have multiple) |
91 | | - if "status:" in l or l.startswith("domain status"): |
92 | | - if statut is None: |
93 | | - parts = line.split(":", 1) |
94 | | - if len(parts) == 2: |
95 | | - statut = parts[1].strip() |
96 | | - # Check for pendingDelete and redemptionPeriod in any status line |
97 | | - if "pendingdelete" in l: |
98 | | - pendingDelete = True |
99 | | - if "redemptionperiod" in l: |
100 | | - redemptionPeriod = True |
101 | | - continue |
102 | | - |
103 | | - # Fallback regex for Registrar lines like 'Registrar Name' without colon |
104 | | - if registrar is None: |
105 | | - m = re.search(r"registrar\s+([\w\-\. ]{3,})", raw, re.IGNORECASE) |
106 | | - if m: |
107 | | - registrar = m.group(1).strip() |
108 | | - |
109 | | - return { |
110 | | - "statut": statut, |
111 | | - "creation_date": creation_date, |
112 | | - "registrar": registrar, |
113 | | - "pendingDelete": pendingDelete, |
114 | | - "redemptionPeriod": redemptionPeriod, |
115 | | - } |
| 49 | + # parser is provided by app.services.whois.parse_whois |
116 | 50 |
|
117 | 51 | if force != 1: |
118 | 52 | cached_data = cache.get(domain) |
119 | 53 | if cached_data: |
120 | | - # enrich from raw before removing it |
121 | | - parsed = parse_whois(cached_data.get("raw"), tld) |
122 | | - # ne pas exposer le champ raw dans la réponse JSON |
| 54 | + # Prefer parsed fields persisted in DB. Only fallback to parsing raw if fields are missing. |
| 55 | + parsed = { |
| 56 | + "statut": cached_data.get("statut"), |
| 57 | + "creation_date": cached_data.get("creation_date"), |
| 58 | + "registrar": cached_data.get("registrar"), |
| 59 | + "pendingDelete": cached_data.get("pendingDelete"), |
| 60 | + "redemptionPeriod": cached_data.get("redemptionPeriod"), |
| 61 | + } |
| 62 | + # If any key is missing/None, parse raw as fallback |
| 63 | + if not any(v is not None for v in parsed.values()): |
| 64 | + parsed = parse_whois(cached_data.get("raw"), tld) |
| 65 | + else: |
| 66 | + # ensure booleans normalized (could be stored as 0/1) |
| 67 | + try: |
| 68 | + parsed["pendingDelete"] = bool(int(parsed["pendingDelete"])) if parsed["pendingDelete"] is not None else False |
| 69 | + except Exception: |
| 70 | + parsed["pendingDelete"] = bool(parsed.get("pendingDelete")) |
| 71 | + try: |
| 72 | + parsed["redemptionPeriod"] = bool(int(parsed["redemptionPeriod"])) if parsed["redemptionPeriod"] is not None else False |
| 73 | + except Exception: |
| 74 | + parsed["redemptionPeriod"] = bool(parsed.get("redemptionPeriod")) |
| 75 | + # do not expose raw in responses |
123 | 76 | cached_data.pop("raw", None) |
124 | 77 | # inject parsed fields so response_model includes them |
125 | 78 | cached_data.update(parsed) |
@@ -158,8 +111,25 @@ def parse_whois(raw: str, tld: str): |
158 | 111 | cached_data = cache.get(domain) |
159 | 112 | if not cached_data: |
160 | 113 | raise HTTPException(status_code=500, detail="Failed to retrieve data from cache after save") |
161 | | - # enrich from raw before removing it (comme pour le cache hit) |
162 | | - parsed = parse_whois(cached_data.get("raw"), tld) |
| 114 | + # Prefer parsed fields persisted in DB. Only fallback to parsing raw if fields are missing. |
| 115 | + parsed = { |
| 116 | + "statut": cached_data.get("statut"), |
| 117 | + "creation_date": cached_data.get("creation_date"), |
| 118 | + "registrar": cached_data.get("registrar"), |
| 119 | + "pendingDelete": cached_data.get("pendingDelete"), |
| 120 | + "redemptionPeriod": cached_data.get("redemptionPeriod"), |
| 121 | + } |
| 122 | + if not any(v is not None for v in parsed.values()): |
| 123 | + parsed = parse_whois(cached_data.get("raw"), tld) |
| 124 | + else: |
| 125 | + try: |
| 126 | + parsed["pendingDelete"] = bool(int(parsed["pendingDelete"])) if parsed["pendingDelete"] is not None else False |
| 127 | + except Exception: |
| 128 | + parsed["pendingDelete"] = bool(parsed.get("pendingDelete")) |
| 129 | + try: |
| 130 | + parsed["redemptionPeriod"] = bool(int(parsed["redemptionPeriod"])) if parsed["redemptionPeriod"] is not None else False |
| 131 | + except Exception: |
| 132 | + parsed["redemptionPeriod"] = bool(parsed.get("redemptionPeriod")) |
163 | 133 | cached_data.pop("raw", None) |
164 | 134 | cached_data.update(parsed) |
165 | 135 | # ensure coherence: if pendingDelete or redemptionPeriod, available must be False |
|
0 commit comments