Backend (FastAPI, SQLAlchemy 2, Alembic, PostgreSQL 16): - организации, VRF, префиксы (дерево, использование, автоназначение), адреса, операторы связи, устройства и типы устройств; JWT, роли admin/viewer; - VRF принадлежит организации (составной FK), смена VRF у префикса переносит поддерево, имя VRF уникально в организации; - журнал аудита: поиск и фильтры, ротация (срок/количество), очистка по паролю с блокировкой, IP клиента и метаданные запроса (X-Forwarded-For только от TRUSTED_PROXIES). UI (web/, без сборки): экраны и диалоги по макетам «IPAM Manager», кликабельные строки реестров, локальные шрифты IBM Plex, собственные выпадающие списки. Окружение: docker-compose (postgres + app), миграции Alembic 0001-0004, scripts/gen_env.py, scripts/seed_demo.py, 11 автотестов (pytest). Документация: README.md и docs/changes/001-005 (планы и итоги). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
37 lines
1.7 KiB
Python
37 lines
1.7 KiB
Python
"""VRF принадлежит организации префикса (составной FK); имя VRF уникально в организации без учёта регистра
|
|
|
|
Revision ID: 0002
|
|
Revises: 0001
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = "0002"
|
|
down_revision = "0001"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
bind = op.get_bind()
|
|
dup = bind.execute(sa.text(
|
|
"SELECT organization_id, lower(name) FROM vrfs GROUP BY 1, 2 HAVING count(*) > 1")).all()
|
|
if dup:
|
|
raise RuntimeError(f"Есть VRF с совпадающими (без учёта регистра) именами в одной организации: {dup}")
|
|
bad = bind.execute(sa.text(
|
|
"SELECT p.id FROM prefixes p JOIN vrfs v ON v.id = p.vrf_id WHERE v.organization_id <> p.organization_id")).all()
|
|
if bad:
|
|
raise RuntimeError(f"Префиксы с VRF чужой организации: {[r[0] for r in bad]}")
|
|
|
|
op.create_unique_constraint("uq_vrfs_id_organization_id", "vrfs", ["id", "organization_id"])
|
|
op.create_foreign_key("fk_prefixes_vrf_org", "prefixes", "vrfs", ["vrf_id", "organization_id"], ["id", "organization_id"])
|
|
op.drop_constraint("vrfs_organization_id_name_key", "vrfs", type_="unique")
|
|
op.create_index("uq_vrfs_org_lower_name", "vrfs", ["organization_id", sa.text("lower(name)")], unique=True)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("uq_vrfs_org_lower_name", table_name="vrfs")
|
|
op.create_unique_constraint("vrfs_organization_id_name_key", "vrfs", ["organization_id", "name"])
|
|
op.drop_constraint("fk_prefixes_vrf_org", "prefixes", type_="foreignkey")
|
|
op.drop_constraint("uq_vrfs_id_organization_id", "vrfs", type_="unique")
|