Initial Commit : Network Map
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
-- Network Infrastructure Map — V1 schema.
|
||||
-- Adds 7 tables (sites, locations, devices, ports, links, vlans, imports)
|
||||
-- plus a private Storage bucket for uploaded source documents.
|
||||
-- RLS: read for admin/it_staff/dispatcher/programmer; write for admin/it_staff only.
|
||||
-- See plan: C:\Users\marcr\.claude\plans\i-m-thinking-of-nested-lagoon.md
|
||||
|
||||
-- =============================================================================
|
||||
-- TABLES
|
||||
-- =============================================================================
|
||||
|
||||
-- Sites: top-level physical locations (campus, building treated standalone)
|
||||
CREATE TABLE IF NOT EXISTS public.network_sites (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name text NOT NULL,
|
||||
address text,
|
||||
notes text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
created_by uuid REFERENCES public.profiles(id)
|
||||
);
|
||||
|
||||
-- Locations: nested physical hierarchy within a site
|
||||
CREATE TABLE IF NOT EXISTS public.network_locations (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
site_id uuid NOT NULL REFERENCES public.network_sites(id) ON DELETE CASCADE,
|
||||
parent_id uuid REFERENCES public.network_locations(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
kind text NOT NULL CHECK (kind IN ('building','floor','room','rack','wall_jack','other')),
|
||||
position_label text,
|
||||
notes text,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_network_locations_site ON public.network_locations(site_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_network_locations_parent ON public.network_locations(parent_id);
|
||||
|
||||
-- Devices: switches, routers, APs, endpoints, patch panels
|
||||
CREATE TABLE IF NOT EXISTS public.network_devices (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name text NOT NULL,
|
||||
kind text NOT NULL CHECK (kind IN
|
||||
('router','switch','ap','firewall','server','endpoint','patch_panel','other')),
|
||||
role text CHECK (role IN ('core','distribution','access','edge','endpoint')),
|
||||
vendor text,
|
||||
model text,
|
||||
serial text,
|
||||
mgmt_ip inet,
|
||||
mac macaddr,
|
||||
location_id uuid REFERENCES public.network_locations(id) ON DELETE SET NULL,
|
||||
import_source text NOT NULL DEFAULT 'manual'
|
||||
CHECK (import_source IN ('manual','ai_import','agent')),
|
||||
notes text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_network_devices_location ON public.network_devices(location_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_network_devices_role ON public.network_devices(role);
|
||||
CREATE INDEX IF NOT EXISTS idx_network_devices_kind ON public.network_devices(kind);
|
||||
|
||||
-- Ports: physical/logical ports on a device
|
||||
CREATE TABLE IF NOT EXISTS public.network_ports (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
device_id uuid NOT NULL REFERENCES public.network_devices(id) ON DELETE CASCADE,
|
||||
port_number text NOT NULL,
|
||||
port_kind text CHECK (port_kind IN ('rj45','sfp','sfp_plus','console','power','wireless','virtual','other')),
|
||||
speed_mbps integer,
|
||||
access_vlan integer,
|
||||
is_trunk boolean NOT NULL DEFAULT false,
|
||||
trunk_vlans integer[],
|
||||
notes text,
|
||||
UNIQUE (device_id, port_number)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_network_ports_device ON public.network_ports(device_id);
|
||||
|
||||
-- Links: port-to-port edges. Canonical ordering (port_a < port_b) prevents A↔B/B↔A dupes.
|
||||
CREATE TABLE IF NOT EXISTS public.network_links (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
port_a uuid NOT NULL REFERENCES public.network_ports(id) ON DELETE CASCADE,
|
||||
port_b uuid NOT NULL REFERENCES public.network_ports(id) ON DELETE CASCADE,
|
||||
link_kind text CHECK (link_kind IN ('copper','fiber','wireless','virtual','unknown')),
|
||||
cable_label text,
|
||||
notes text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
CHECK (port_a < port_b),
|
||||
UNIQUE (port_a, port_b)
|
||||
);
|
||||
|
||||
-- VLANs (org-scoped, named)
|
||||
CREATE TABLE IF NOT EXISTS public.network_vlans (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
vlan_id integer NOT NULL UNIQUE,
|
||||
name text NOT NULL,
|
||||
description text,
|
||||
color text
|
||||
);
|
||||
|
||||
-- Import sessions: each upload + extraction state + raw AI result for audit/replay
|
||||
CREATE TABLE IF NOT EXISTS public.network_imports (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
storage_path text NOT NULL,
|
||||
file_kind text NOT NULL
|
||||
CHECK (file_kind IN ('pdf','image','vsdx','csv','xlsx','docx','other')),
|
||||
status text NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending','extracting','review','applied','discarded','failed')),
|
||||
ai_result jsonb,
|
||||
error_message text,
|
||||
applied_device_ids uuid[],
|
||||
applied_link_ids uuid[],
|
||||
applied_at timestamptz,
|
||||
created_by uuid NOT NULL REFERENCES public.profiles(id),
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_network_imports_status ON public.network_imports(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_network_imports_created_by ON public.network_imports(created_by);
|
||||
|
||||
-- =============================================================================
|
||||
-- updated_at TRIGGERS
|
||||
-- =============================================================================
|
||||
-- Reuses common pattern from prior Tasq migrations.
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.network_map_set_updated_at()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_network_sites_updated_at ON public.network_sites;
|
||||
CREATE TRIGGER trg_network_sites_updated_at
|
||||
BEFORE UPDATE ON public.network_sites
|
||||
FOR EACH ROW EXECUTE FUNCTION public.network_map_set_updated_at();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_network_devices_updated_at ON public.network_devices;
|
||||
CREATE TRIGGER trg_network_devices_updated_at
|
||||
BEFORE UPDATE ON public.network_devices
|
||||
FOR EACH ROW EXECUTE FUNCTION public.network_map_set_updated_at();
|
||||
|
||||
-- =============================================================================
|
||||
-- ROW LEVEL SECURITY
|
||||
-- =============================================================================
|
||||
-- Pattern matches supabase/migrations/20260223090000_services_read_only_for_standard_it_dispatcher.sql
|
||||
-- READ: admin + it_staff + dispatcher + programmer
|
||||
-- WRITE: admin + it_staff
|
||||
|
||||
ALTER TABLE public.network_sites ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.network_locations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.network_devices ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.network_ports ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.network_links ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.network_vlans ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.network_imports ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- ----- network_sites -----
|
||||
DROP POLICY IF EXISTS "Network sites: select" ON public.network_sites;
|
||||
CREATE POLICY "Network sites: select" ON public.network_sites
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid()
|
||||
AND p.role IN ('admin','it_staff','dispatcher','programmer'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network sites: write" ON public.network_sites;
|
||||
CREATE POLICY "Network sites: write" ON public.network_sites
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
-- ----- network_locations -----
|
||||
DROP POLICY IF EXISTS "Network locations: select" ON public.network_locations;
|
||||
CREATE POLICY "Network locations: select" ON public.network_locations
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid()
|
||||
AND p.role IN ('admin','it_staff','dispatcher','programmer'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network locations: write" ON public.network_locations;
|
||||
CREATE POLICY "Network locations: write" ON public.network_locations
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
-- ----- network_devices -----
|
||||
DROP POLICY IF EXISTS "Network devices: select" ON public.network_devices;
|
||||
CREATE POLICY "Network devices: select" ON public.network_devices
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid()
|
||||
AND p.role IN ('admin','it_staff','dispatcher','programmer'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network devices: write" ON public.network_devices;
|
||||
CREATE POLICY "Network devices: write" ON public.network_devices
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
-- ----- network_ports -----
|
||||
DROP POLICY IF EXISTS "Network ports: select" ON public.network_ports;
|
||||
CREATE POLICY "Network ports: select" ON public.network_ports
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid()
|
||||
AND p.role IN ('admin','it_staff','dispatcher','programmer'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network ports: write" ON public.network_ports;
|
||||
CREATE POLICY "Network ports: write" ON public.network_ports
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
-- ----- network_links -----
|
||||
DROP POLICY IF EXISTS "Network links: select" ON public.network_links;
|
||||
CREATE POLICY "Network links: select" ON public.network_links
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid()
|
||||
AND p.role IN ('admin','it_staff','dispatcher','programmer'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network links: write" ON public.network_links;
|
||||
CREATE POLICY "Network links: write" ON public.network_links
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
-- ----- network_vlans -----
|
||||
DROP POLICY IF EXISTS "Network vlans: select" ON public.network_vlans;
|
||||
CREATE POLICY "Network vlans: select" ON public.network_vlans
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid()
|
||||
AND p.role IN ('admin','it_staff','dispatcher','programmer'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network vlans: write" ON public.network_vlans;
|
||||
CREATE POLICY "Network vlans: write" ON public.network_vlans
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
-- ----- network_imports -----
|
||||
DROP POLICY IF EXISTS "Network imports: select" ON public.network_imports;
|
||||
CREATE POLICY "Network imports: select" ON public.network_imports
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid()
|
||||
AND p.role IN ('admin','it_staff','dispatcher','programmer'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network imports: write" ON public.network_imports;
|
||||
CREATE POLICY "Network imports: write" ON public.network_imports
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
) WITH CHECK (
|
||||
EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- STORAGE BUCKET (private) for uploaded source documents
|
||||
-- =============================================================================
|
||||
-- The Edge Function `extract_topology` uses the service role to read from this
|
||||
-- bucket. Authenticated admin/it_staff clients upload via signed URLs or with
|
||||
-- their RLS-bound JWT.
|
||||
|
||||
INSERT INTO storage.buckets (id, name, public)
|
||||
VALUES ('network-imports', 'network-imports', false)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Storage policies: only admin/it_staff can upload; same group + dispatcher/programmer can read.
|
||||
DROP POLICY IF EXISTS "Network imports bucket: select" ON storage.objects;
|
||||
CREATE POLICY "Network imports bucket: select" ON storage.objects
|
||||
FOR SELECT USING (
|
||||
bucket_id = 'network-imports'
|
||||
AND EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid()
|
||||
AND p.role IN ('admin','it_staff','dispatcher','programmer'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network imports bucket: write" ON storage.objects;
|
||||
CREATE POLICY "Network imports bucket: write" ON storage.objects
|
||||
FOR INSERT WITH CHECK (
|
||||
bucket_id = 'network-imports'
|
||||
AND EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network imports bucket: update" ON storage.objects;
|
||||
CREATE POLICY "Network imports bucket: update" ON storage.objects
|
||||
FOR UPDATE USING (
|
||||
bucket_id = 'network-imports'
|
||||
AND EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
|
||||
DROP POLICY IF EXISTS "Network imports bucket: delete" ON storage.objects;
|
||||
CREATE POLICY "Network imports bucket: delete" ON storage.objects
|
||||
FOR DELETE USING (
|
||||
bucket_id = 'network-imports'
|
||||
AND EXISTS (SELECT 1 FROM public.profiles p
|
||||
WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
|
||||
);
|
||||
Reference in New Issue
Block a user