create extension if not exists vector; create table public.users ( id uuid primary key references auth.users(id) on delete cascade, email text not null, display_name text, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table public.projects ( id uuid primary key default gen_random_uuid(), user_id uuid not null references public.users(id) on delete cascade, name text not null, description text, is_global boolean not null default false, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table public.folders ( id uuid primary key default gen_random_uuid(), project_id uuid not null references public.projects(id) on delete cascade, name text not null, description text, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table public.memories ( id uuid primary key default gen_random_uuid(), user_id uuid not null references public.users(id) on delete cascade, project_id uuid not null references public.projects(id) on delete cascade, folder_id uuid references public.folders(id) on delete set null, content text not null, tags jsonb not null default '[]'::jsonb, metadata jsonb not null default '{}'::jsonb, embedding vector(1536), created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create index memories_user_id_idx on public.memories(user_id); create index memories_project_id_idx on public.memories(project_id); create index memories_folder_id_idx on public.memories(folder_id); create index memories_tags_idx on public.memories using gin(tags); create index memories_embedding_idx on public.memories using ivfflat (embedding vector_cosine_ops) with (lists = 100); create index projects_user_id_idx on public.projects(user_id); create index folders_project_id_idx on public.folders(project_id); create or replace function public.handle_updated_at() returns trigger as $$ begin new.updated_at = now(); return new; end; $$ language plpgsql; create trigger users_updated_at before update on public.users for each row execute function public.handle_updated_at(); create trigger projects_updated_at before update on public.projects for each row execute function public.handle_updated_at(); create trigger folders_updated_at before update on public.folders for each row execute function public.handle_updated_at(); create trigger memories_updated_at before update on public.memories for each row execute function public.handle_updated_at(); alter table public.users enable row level security; alter table public.projects enable row level security; alter table public.folders enable row level security; alter table public.memories enable row level security; create policy "Users can view own profile" on public.users for select using (auth.uid() = id); create policy "Users can update own profile" on public.users for update using (auth.uid() = id); create policy "Users can view own projects" on public.projects for select using (auth.uid() = user_id); create policy "Users can create own projects" on public.projects for insert with check (auth.uid() = user_id); create policy "Users can update own projects" on public.projects for update using (auth.uid() = user_id); create policy "Users can delete own projects" on public.projects for delete using (auth.uid() = user_id); create policy "Users can view folders in own projects" on public.folders for select using ( exists ( select 1 from public.projects where projects.id = folders.project_id and projects.user_id = auth.uid() ) ); create policy "Users can create folders in own projects" on public.folders for insert with check ( exists ( select 1 from public.projects where projects.id = folders.project_id and projects.user_id = auth.uid() ) ); create policy "Users can update folders in own projects" on public.folders for update using ( exists ( select 1 from public.projects where projects.id = folders.project_id and projects.user_id = auth.uid() ) ); create policy "Users can delete folders in own projects" on public.folders for delete using ( exists ( select 1 from public.projects where projects.id = folders.project_id and projects.user_id = auth.uid() ) ); create policy "Users can view own memories" on public.memories for select using (auth.uid() = user_id); create policy "Users can create own memories" on public.memories for insert with check (auth.uid() = user_id); create policy "Users can update own memories" on public.memories for update using (auth.uid() = user_id); create policy "Users can delete own memories" on public.memories for delete using (auth.uid() = user_id); create or replace function public.handle_new_user() returns trigger as $$ begin insert into public.users (id, email) values (new.id, new.email); return new; end; $$ language plpgsql security definer; create trigger on_auth_user_created after insert on auth.users for each row execute function public.handle_new_user(); create or replace function public.search_memories( query_embedding vector(1536), match_threshold float default 0.7, match_count int default 10, filter_project_id uuid default null, filter_folder_id uuid default null ) returns table ( id uuid, content text, project_id uuid, folder_id uuid, tags jsonb, metadata jsonb, similarity float, created_at timestamptz, updated_at timestamptz ) language plpgsql security definer as $$ begin return query select m.id, m.content, m.project_id, m.folder_id, m.tags, m.metadata, 1 - (m.embedding <=> query_embedding) as similarity, m.created_at, m.updated_at from public.memories m where m.user_id = auth.uid() and m.embedding is not null and 1 - (m.embedding <=> query_embedding) > match_threshold and (filter_project_id is null or m.project_id = filter_project_id) and (filter_folder_id is null or m.folder_id = filter_folder_id) order by m.embedding <=> query_embedding limit match_count; end; $$;