-- ========================================== -- Chat Switchboard โ€” 007 Notes & Links -- ========================================== -- Notes with full-text search, wikilink graph edges. -- ICD ยง6 (Notes) -- ========================================== -- ========================================= -- NOTES -- ========================================= CREATE TABLE IF NOT EXISTS notes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, title VARCHAR(500) NOT NULL, content TEXT DEFAULT '', folder_path TEXT DEFAULT '/', tags TEXT[] DEFAULT '{}', metadata JSONB DEFAULT '{}', source_channel_id UUID REFERENCES channels(id) ON DELETE SET NULL, source_message_id UUID, team_id UUID REFERENCES teams(id) ON DELETE SET NULL, search_vector TSVECTOR, embedding VECTOR(3072), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_notes_user ON notes(user_id); CREATE INDEX IF NOT EXISTS idx_notes_search ON notes USING GIN(search_vector); CREATE INDEX IF NOT EXISTS idx_notes_folder ON notes(user_id, folder_path); CREATE INDEX IF NOT EXISTS idx_notes_tags ON notes USING GIN(tags); CREATE INDEX IF NOT EXISTS idx_notes_updated ON notes(user_id, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_notes_team ON notes(team_id) WHERE team_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_notes_source_msg ON notes(source_message_id) WHERE source_message_id IS NOT NULL; CREATE OR REPLACE FUNCTION notes_search_update_fn() RETURNS TRIGGER AS $$ BEGIN NEW.search_vector := setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.title, '')), 'A') || setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.content, '')), 'B'); NEW.updated_at := NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS notes_search_update ON notes; CREATE TRIGGER notes_search_update BEFORE INSERT OR UPDATE OF title, content ON notes FOR EACH ROW EXECUTE FUNCTION notes_search_update_fn(); -- ========================================= -- NOTE LINKS (v0.17.3 โ€” wikilink graph) -- ========================================= CREATE TABLE IF NOT EXISTS note_links ( source_note_id UUID NOT NULL REFERENCES notes(id) ON DELETE CASCADE, target_note_id UUID REFERENCES notes(id) ON DELETE CASCADE, target_title TEXT NOT NULL, display_text TEXT, is_transclusion BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT now(), PRIMARY KEY (source_note_id, target_title) ); CREATE INDEX IF NOT EXISTS idx_note_links_target ON note_links(target_note_id) WHERE target_note_id IS NOT NULL; COMMENT ON TABLE note_links IS 'Directed links between notes, extracted from [[wikilink]] syntax on save'; COMMENT ON COLUMN note_links.target_note_id IS 'NULL for unresolved links (target note does not exist yet)'; COMMENT ON COLUMN note_links.target_title IS 'Raw [[title]] text โ€” preserved for re-resolution after renames'; COMMENT ON COLUMN note_links.is_transclusion IS 'true for ![[embed]] syntax, false for regular [[link]]';