Оба места утверждали, что у Домклика один выделенный резидентный прокси и пула нет. Это перестало быть правдой ещё в #2800 (миграция 253 сняла резервацию узла), но текст остался — и именно на него опирался тикет #3189, поставленный под «калибровку» ограничения, которого не существует. Свип к тому же ходит через пул давно (serp.py:359), а бэкфилл подключён к нему в PR #3222. Заодно докстринг называл не тот ограничитель: свип кладёт не счётчик провалов lease, а break по первому DomClickBlockedError (#2854) — до ротации дело не доходит ни при каком счётчике, отсюда buckets_completed=0. Только комментарии, поведение не меняется. Миграция 175 уже применена, а _schema_migrations трекает по имени файла без checksum — правка текста её не перезапустит.
113 lines
7.1 KiB
PL/PgSQL
113 lines
7.1 KiB
PL/PgSQL
-- 145_insight.sql
|
||
-- #948 Part A (ТЗ §7.13/§8.9): manual-entry "insight" — аналитик пишет свободную
|
||
-- заметку/intel про локацию или участок, с пометкой «непублично» (is_confidential).
|
||
--
|
||
-- Access control: backend rbac_guard (app/main.py) хард-блокирует ТОЛЬКО
|
||
-- /api/v1/admin/*. На /api/v1/insights pilot отсекается FRONTEND-овым RouteGuard
|
||
-- (allowed_paths из /me) + Caddy, НЕ бэкендом; до эндпоинта доходят analyst+admin.
|
||
-- is_confidential — хранимая маркировка (per-record backend-gate НЕТ; будущий gate
|
||
-- публичной выдачи), сейчас просто хранимый+фильтруемый флаг (политика #962: analyst
|
||
-- видит конфиденциальные данные). created_by фиксирует автора (из X-Authenticated-User).
|
||
--
|
||
-- Location ref: пока plain TEXT district + опциональный cad_num (ссылка на участок).
|
||
-- Part B (#948, §8.2) промоутит district в Location FK — НЕ в этом таске.
|
||
--
|
||
-- Audit (§19, интеграция с #962): WRITE-запросы (POST/PUT/DELETE) аудируются
|
||
-- middleware app/core/audit_middleware.py с action='insight_write'. Поэтому ниже
|
||
-- (блок 4) аддитивно расширяем CHECK audit_log.action, чтобы 'insight_write'
|
||
-- проходил констрейнт. Это idempotent ALTER (DROP IF EXISTS + ADD) — НЕ правит
|
||
-- 144_audit_log.sql (тот уже применён на проде/смёржен; правка применённой
|
||
-- миграции не переиграется через _schema_migrations).
|
||
--
|
||
-- Deploy: auto-applied deploy.yml через _schema_migrations (ровно один раз NN=145).
|
||
-- Idempotent: CREATE TABLE/INDEX IF NOT EXISTS + guarded CHECK.
|
||
|
||
BEGIN;
|
||
|
||
-- ── 1. Таблица insight ────────────────────────────────────────────────────────
|
||
CREATE TABLE IF NOT EXISTS insight (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
created_by TEXT NOT NULL, -- автор, из X-Authenticated-User
|
||
title TEXT NOT NULL, -- заголовок заметки
|
||
body TEXT NOT NULL, -- тело заметки (free-form intel)
|
||
category TEXT, -- competition|permitting|demand|risk|other
|
||
is_confidential BOOLEAN NOT NULL DEFAULT FALSE, -- «непублично» (§7.13)
|
||
district TEXT, -- location ref (Part B → Location FK)
|
||
cad_num TEXT, -- опциональная ссылка на участок
|
||
lon DOUBLE PRECISION CHECK (lon IS NULL OR lon BETWEEN -180 AND 180),
|
||
lat DOUBLE PRECISION CHECK (lat IS NULL OR lat BETWEEN -90 AND 90),
|
||
-- Опциональный map-pin: GENERATED из lon/lat (как user_custom_pois.geom #254).
|
||
-- NULL если координаты не заданы (обе должны быть заданы вместе).
|
||
geom GEOMETRY(POINT, 4326) GENERATED ALWAYS AS (
|
||
CASE
|
||
WHEN lon IS NOT NULL AND lat IS NOT NULL
|
||
THEN ST_SetSRID(ST_MakePoint(lon, lat), 4326)
|
||
END
|
||
) STORED,
|
||
source TEXT, -- источник intel (опционально)
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE insight IS
|
||
'Manual-entry аналитические заметки/intel про локацию или участок (#948 Part A, '
|
||
'ТЗ §7.13/§8.9). is_confidential = маркировка «непублично». created_by = автор '
|
||
'из X-Authenticated-User. district — plain TEXT (Part B промоутит в Location FK).';
|
||
COMMENT ON COLUMN insight.is_confidential IS
|
||
'«Непублично» (§7.13): маркировка чувствительной заметки. Сейчас хранимый+'
|
||
'фильтруемый флаг; будущий gate публичной выдачи.';
|
||
COMMENT ON COLUMN insight.category IS
|
||
'Свободная категория (рекоменд.: competition|permitting|demand|risk|other).';
|
||
COMMENT ON COLUMN insight.geom IS
|
||
'Опциональный map-pin, GENERATED из lon/lat. NULL если координаты не заданы.';
|
||
|
||
-- ── 2. CHECK на category (guarded — idempotent при ре-apply) ──────────────────
|
||
-- Мягкий enum: разрешаем рекомендованные значения + NULL. Расширять список —
|
||
-- через новую миграцию (DROP IF EXISTS + ADD), не правкой этой.
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (
|
||
SELECT 1 FROM pg_constraint WHERE conname = 'ck_insight_category'
|
||
) THEN
|
||
ALTER TABLE insight
|
||
ADD CONSTRAINT ck_insight_category
|
||
CHECK (
|
||
category IS NULL
|
||
OR category IN ('competition', 'permitting', 'demand', 'risk', 'other')
|
||
);
|
||
END IF;
|
||
END
|
||
$$;
|
||
|
||
-- ── 3. Индексы: фильтры list-эндпоинта ────────────────────────────────────────
|
||
CREATE INDEX IF NOT EXISTS idx_insight_district
|
||
ON insight (district);
|
||
CREATE INDEX IF NOT EXISTS idx_insight_cad_num
|
||
ON insight (cad_num);
|
||
CREATE INDEX IF NOT EXISTS idx_insight_is_confidential
|
||
ON insight (is_confidential);
|
||
CREATE INDEX IF NOT EXISTS idx_insight_created_by
|
||
ON insight (created_by);
|
||
-- Опциональный geo-lookup (как user_custom_pois_geom_gist #254).
|
||
CREATE INDEX IF NOT EXISTS idx_insight_geom_gist
|
||
ON insight USING GIST (geom);
|
||
|
||
-- ── 4. Расширяем CHECK audit_log.action → +'insight_write' (§19, #962) ─────────
|
||
-- Middleware аудирует WRITE insight'ов как action='insight_write'. 144_audit_log
|
||
-- разрешал только analyze|forecast|export|other; добавляем 'insight_write'.
|
||
-- Idempotent: DROP IF EXISTS + ADD пере-создаёт констрейнт с расширенным набором
|
||
-- (повторный apply этой миграции даст тот же результат). Таблица audit_log создана
|
||
-- в 144 (она в HEAD/проде до этой миграции) — но guard'имся to_regclass на случай
|
||
-- среды, где 144 ещё не накатан, чтобы 145 не падал.
|
||
DO $$
|
||
BEGIN
|
||
IF to_regclass('public.audit_log') IS NOT NULL THEN
|
||
ALTER TABLE audit_log DROP CONSTRAINT IF EXISTS ck_audit_log_action;
|
||
ALTER TABLE audit_log
|
||
ADD CONSTRAINT ck_audit_log_action
|
||
CHECK (action IN ('analyze', 'forecast', 'export', 'insight_write', 'other'));
|
||
END IF;
|
||
END
|
||
$$;
|
||
|
||
COMMIT;
|