Netpulse_SasS/server/migrations/0021_map_access_icons.sql
byrsapty 205dd5e079 Пакування: runner міграцій і вшитий у бінарник фронтенд
netpulse-migrate замість PowerShell-скрипта: у контейнері немає ані
psql, ані PowerShell, а тягнути клієнт Postgres в образ заради одного
запуску — це половина дистрибутива на порожньому місці.

Міграції вшиті через embed і переїхали в server/migrations: embed не
бачить нічого за межами кореня свого модуля, а міграції поруч із
бінарником, який їх накочує, не можуть розійтися версіями.

Накочування під advisory-блокуванням: два інстанси при rolling update
інакше застосували б ту саму міграцію двічі. Кожен файл в одній
транзакції разом із записом у schema_migrations; виняток — continuous
aggregates, які TimescaleDB забороняє в транзакції. Змінена вже
застосована міграція зупиняє запуск: у різних інсталяціях інакше
опиниться різна схема під одним номером.

Перевірено на чистій базі: 23 міграції, 101 таблиця, повторний запуск
каже «схема актуальна».

Веб віддає сам API через embed: на self-hosted це прибирає з інструкції
встановлення цілий компонент. Три політики кешування — назавжди для
assets із хешем у імені, ніколи для index.html, коротко для решти.

Знайдено живим прогоном: невідомий шлях під /api/ віддавав 200 з
index.html, і клієнт падав на розборі HTML як JSON замість чесного 404.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 01:01:55 +03:00

93 lines
4.8 KiB
PL/PgSQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =====================================================================
-- NetPulse :: 0021_map_access_icons.sql
-- Доступ до мап за групами користувачів і власні іконки вузлів.
-- =====================================================================
-- ---------------------------------------------------------------------
-- Хто бачить мапу
-- ---------------------------------------------------------------------
-- Та сама модель, що для хостів (core.group_permissions): роль каже, що
-- людина взагалі вміє робити з мапами, група — з якими саме.
--
-- Мапа без жодного запису доступна всім, хто має maps:read. Це свідомо
-- інакше, ніж у Zabbix: у типовій інсталяції мапа одна на всіх, і
-- вимагати налаштувати доступ до неї означало б зробити типовий випадок
-- найдовшим.
CREATE TABLE topo.map_permissions (
map_id uuid NOT NULL REFERENCES topo.maps(id) ON DELETE CASCADE,
group_id uuid NOT NULL REFERENCES core.user_groups(id) ON DELETE CASCADE,
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
level core.access_level NOT NULL DEFAULT 'read',
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (map_id, group_id)
);
CREATE INDEX map_permissions_group_idx ON topo.map_permissions (group_id);
ALTER TABLE topo.map_permissions ENABLE ROW LEVEL SECURITY;
ALTER TABLE topo.map_permissions FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON topo.map_permissions
USING (tenant_id = core.current_tenant())
WITH CHECK (tenant_id = core.current_tenant());
GRANT SELECT, INSERT, UPDATE, DELETE ON topo.map_permissions
TO netpulse_app, netpulse_worker;
-- Рівень доступу людини до конкретної мапи.
--
-- Правила ті самі, що для хостів: deny перекриває все, потім write,
-- потім read. Відсутність записів для мапи означає «доступна всім» —
-- інакше кожна нова мапа була б невидимою до окремого налаштування.
CREATE OR REPLACE FUNCTION topo.map_access_level(p_user uuid, p_map uuid)
RETURNS core.access_level
LANGUAGE sql STABLE AS $$
SELECT CASE
WHEN NOT EXISTS (SELECT 1 FROM topo.map_permissions WHERE map_id = p_map)
THEN 'write'::core.access_level
WHEN EXISTS (
SELECT 1 FROM topo.map_permissions mp
JOIN core.user_group_members gm ON gm.group_id = mp.group_id
WHERE mp.map_id = p_map AND gm.user_id = p_user AND mp.level = 'deny'
) THEN 'deny'::core.access_level
WHEN EXISTS (
SELECT 1 FROM topo.map_permissions mp
JOIN core.user_group_members gm ON gm.group_id = mp.group_id
WHERE mp.map_id = p_map AND gm.user_id = p_user AND mp.level = 'write'
) THEN 'write'::core.access_level
WHEN EXISTS (
SELECT 1 FROM topo.map_permissions mp
JOIN core.user_group_members gm ON gm.group_id = mp.group_id
WHERE mp.map_id = p_map AND gm.user_id = p_user AND mp.level = 'read'
) THEN 'read'::core.access_level
ELSE 'deny'::core.access_level
END
$$;
-- ---------------------------------------------------------------------
-- Власні іконки
-- ---------------------------------------------------------------------
-- Картинка зберігається в БД, а не у файловій системі: інсталяція з
-- кількох процесів інакше вимагала б спільного тому, а бекап продукту
-- перестав би бути бекапом бази.
--
-- SVG і PNG до 256 КБ. Більше на мапі не потрібно: іконка вузла — це
-- 3264 пікселі, і мегабайтний PNG там означає лише повільне полотно.
CREATE TABLE topo.icons (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
name text NOT NULL,
mime text NOT NULL CHECK (mime IN ('image/svg+xml','image/png','image/jpeg','image/webp')),
bytes bytea NOT NULL CHECK (octet_length(bytes) <= 262144),
created_by uuid REFERENCES core.users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, name)
);
ALTER TABLE topo.icons ENABLE ROW LEVEL SECURITY;
ALTER TABLE topo.icons FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON topo.icons
USING (tenant_id = core.current_tenant())
WITH CHECK (tenant_id = core.current_tenant());
GRANT SELECT, INSERT, UPDATE, DELETE ON topo.icons TO netpulse_app, netpulse_worker;