-- Widget kinds are intentionally data-driven. Removing enum-like checks means -- future widget types do not require a table rewrite merely to exist. ALTER TABLE osint.widgets DROP CONSTRAINT IF EXISTS widgets_widget_type_check; ALTER TABLE osint.playthrough_widgets DROP CONSTRAINT IF EXISTS playthrough_widgets_widget_type_check; ALTER TABLE osint.widgets ADD COLUMN published_at TIMESTAMPTZ; UPDATE osint.widgets SET published_at = event_date::timestamp AT TIME ZONE 'UTC' WHERE widget_type = 'document' AND event_date IS NOT NULL; CREATE TABLE osint.widget_relations ( id TEXT PRIMARY KEY, level_id TEXT NOT NULL REFERENCES osint.levels(id) ON DELETE CASCADE, from_widget_id TEXT NOT NULL REFERENCES osint.widgets(id) ON DELETE CASCADE, to_widget_id TEXT NOT NULL REFERENCES osint.widgets(id) ON DELETE CASCADE, relation_type TEXT NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, config JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (from_widget_id, to_widget_id, relation_type) ); CREATE INDEX widget_relations_from_idx ON osint.widget_relations(level_id, from_widget_id, relation_type); CREATE INDEX widget_relations_to_idx ON osint.widget_relations(level_id, to_widget_id, relation_type); CREATE TABLE osint.playthrough_widget_relations ( id TEXT PRIMARY KEY, playthrough_id TEXT NOT NULL REFERENCES osint.playthroughs(id) ON DELETE CASCADE, from_widget_id TEXT NOT NULL, to_widget_id TEXT NOT NULL, relation_type TEXT NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, config JSONB NOT NULL DEFAULT '{}'::jsonb, UNIQUE (playthrough_id, from_widget_id, to_widget_id, relation_type) ); UPDATE osint.widgets SET widget_type = 'folder' WHERE widget_type = 'evidence'; UPDATE osint.playthrough_widgets SET widget_type = 'folder' WHERE widget_type = 'evidence'; INSERT INTO osint.widget_relations (id, level_id, from_widget_id, to_widget_id, relation_type) SELECT 'contains:' || id || ':' || source_widget_id, level_id, id, source_widget_id, 'contains' FROM osint.widgets WHERE widget_type = 'folder' AND source_widget_id IS NOT NULL ON CONFLICT (from_widget_id, to_widget_id, relation_type) DO NOTHING; INSERT INTO osint.playthrough_widget_relations (id, playthrough_id, from_widget_id, to_widget_id, relation_type) SELECT 'contains:' || playthrough_id || ':' || id || ':' || source_widget_id, playthrough_id, id, source_widget_id, 'contains' FROM osint.playthrough_widgets WHERE widget_type = 'folder' AND source_widget_id IS NOT NULL ON CONFLICT (playthrough_id, from_widget_id, to_widget_id, relation_type) DO NOTHING; COMMENT ON COLUMN osint.widgets.published_at IS 'Publication/creation time of a source document; chronology belongs to documents, not folders'; COMMENT ON TABLE osint.widget_relations IS 'Generic authored widget graph; contains is the first relation type';