-- 201_purge_dead_mobileproxy_proxies.sql -- Issue #2613 — выпилить мёртвые узлы mobileproxy из пула scrape_proxies -- вместе с чужим API-ключом, который лежал у них в rotate_url. -- -- WHY: -- Владелец подтвердил: подписка mobileproxy закрыта, продлевать не будут. -- Прямая проба каждого узла из контейнера tradein-scraper (2026-08-01) -- подтверждает смерть: id 2 — connection refused, id 3/4/5 — 407 Proxy -- Authentication Required. Последняя успешная проверка (last_check_at) у -- всех четырёх — 4-9 июля, все четыре enabled=false, consecutive_fails=5. -- -- Две причины удалить, вторая важнее: -- 1. Мёртвые узлы засоряют пул и его health-метрики. -- 2. rotate_url у трёх из четырёх строк (id 3, 4, 5) хранит открытым -- текстом чужой ключ провайдера в query-параметре ссылки ротации -- (https://changeip.mobileproxy.space/?proxy_key=...). Именно из-за -- неоднородности этой колонки (вперемешку с ASocks-строками, где -- rotate_url — наш собственный API-эндпоинт БЕЗ секрета в URL, -- авторизация Bearer-заголовком) глубокое ревью PR #2611 нашло -- блокер: вызов ротации для такой строки отправил бы НАШ токен -- ASocks на changeip.mobileproxy.space. Пин хоста в #2611 уже -- закрывает саму уязвимость, но чужой секрет в базе держать незачем. -- -- ПОЧЕМУ DELETE, А НЕ UPDATE (очистка полей + enabled=false): -- Единственный FK, ссылающийся на scrape_proxies — scrape_proxy_rotations -- .proxy_id (заведён 198_scrape_proxy_rotations.sql), delete_rule NO ACTION. -- На момент миграции (замер ниже) в scrape_proxy_rotations нет НИ ОДНОЙ -- строки вообще — таблица введена в этом же цикле работ (#2600 п.5) и -- ручная ротация ни разу не запускалась. DELETE четырёх строк scrape_proxies -- ничего не упирает. Если бы к строкам 2-5 успела прилипнуть история ротаций -- к моменту применения — DELETE упадёт по FK-violation ВНУТРИ этой же -- транзакции (BEGIN/COMMIT ниже), миграция целиком откатится, deploy -- завершится ошибкой (auto-apply strict, exit 1) без частичного эффекта и -- без порчи данных; отдельного ON DELETE-обработчика не требуется — узлы -- disabled=false уже сейчас, acquire() их не выдаёт (idx_scrape_proxies_pick -- фильтрует по enabled), новых ротаций на них взяться неоткуда до deploy. -- Строки — исторический мусор без ссылок, полное удаление честнее частичной -- очистки (не оставляет призрачную запись мёртвого узла в пуле) и убирает -- секрет из базы целиком, а не только из одной колонки. -- -- Matching (по домену url, НЕ по id): -- id в scrape_proxies разъезжается между средами (bulk-загрузка независима -- per-среда, тот же класс проблемы решён в 199 через host:port-matching). -- Условие — WHERE url LIKE '%mobileproxy.space%' — ловит все четыре узла -- независимо от порта/поддомена (ha./gi./auv./aup.mobileproxy.space) и не -- заденет ASocks-строки (212.8.249.134 / 190.2.145.131 / 175.110.115.153 / -- 109.236.82.42 — IP-адреса, без mobileproxy.space в url вовсе). -- -- ЗАМЕРЫ (SELECT, read-only, прод, перед миграцией, 2026-08-01): -- Строк под условие (url LIKE '%mobileproxy.space%'): 4 (id 2, 3, 4, 5) -- Остаток пула после удаления (url NOT LIKE '%mobileproxy.space%'): -- 4 (id 1, 9, 10, 11) — все ASocks -- Строк в scrape_proxy_rotations на id 2/3/4/5: 0 -- Строк в scrape_proxy_rotations всего (таблица пуста): 0 -- Секрет-паттерн (token|bearer|secret|key=|password, regex -- case-insensitive) в rotate_url ОСТАЮЩИХСЯ 4 строк: 0 совпадений -- (rotate_url остающихся — https://api.asocks.com/unlimited-proxy/ -- /refresh-ip, без query-параметров вообще, авторизация Bearer -- заголовком вне URL, см. 199_scrape_proxies_asocks_rotate_url.sql) -- FK, ссылающиеся на scrape_proxies: ровно один — -- scrape_proxy_rotations.proxy_id -> scrape_proxies.id, delete_rule NO ACTION. -- -- Idempotency: -- Обычный DELETE ... WHERE — повторный прогон находит 0 строк (уже -- удалены), no-op. Весь файл в BEGIN/COMMIT. -- -- ГРАНИЦЫ: НЕ трогает ASocks-строки (id 1, 9, 10, 11) и их rotate_url. НЕ -- трогает переменные окружения (*_PROXY_URL, BROWSER_PROXY_*, -- *_PROXY_ROTATE_URL) — их снятие отдельная задача и НЕ раньше неё, иначе -- при пустом прокси curl_proxy_url отдаёт None = скрапер идёт напрямую с IP -- сервера. НЕ трогает app/services/proxy_pool.py, proxy_rotation.py, -- скраперы. Никакого DDL. -- -- Dependencies: -- 157_scrape_proxies.sql (scrape_proxies.url/rotate_url/enabled). -- 198_scrape_proxy_rotations.sql (FK proxy_id -> scrape_proxies.id, NO ACTION). BEGIN; DELETE FROM scrape_proxies WHERE url LIKE '%mobileproxy.space%'; COMMIT;