--[ Database Schema Version ]-- -- Version: 1.0.0 -- Last Updated: 2024-02-24 -- Description: Core schema setup for ClassConcepts with neoFS filesystem integration -- Dependencies: auth.users (Supabase Auth) --[ Validation ]-- do $$ begin -- Verify required extensions if not exists (select 1 from pg_extension where extname = 'uuid-ossp') then raise exception 'Required extension uuid-ossp is not installed'; end if; -- Verify auth schema exists if not exists (select 1 from information_schema.schemata where schema_name = 'auth') then raise exception 'Required auth schema is not available'; end if; -- Verify storage schema exists if not exists (select 1 from information_schema.schemata where schema_name = 'storage') then raise exception 'Required storage schema is not available'; end if; end $$; --[ 1. Extensions ]-- create extension if not exists "uuid-ossp"; -- Create rpc schema if it doesn't exist create schema if not exists rpc; grant usage on schema rpc to anon, authenticated; -- Create exec_sql function for admin operations create or replace function exec_sql(query text) returns void as $$ begin execute query; end; $$ language plpgsql security definer; -- Create updated_at trigger function create or replace function public.handle_updated_at() returns trigger as $$ begin new.updated_at = timezone('utc'::text, now()); return new; end; $$ language plpgsql security definer; -- Create completed_at trigger function for document artefacts create or replace function public.set_completed_at() returns trigger as $$ begin if NEW.status = 'completed' and OLD.status != 'completed' then NEW.completed_at = now(); end if; return NEW; end; $$ language plpgsql security definer; --[ 5. Core Tables ]-- -- Base user profiles create table if not exists public.profiles ( id uuid primary key references auth.users(id) on delete cascade, email text not null unique, user_type text not null check ( user_type in ( 'teacher', 'student', 'email_teacher', 'email_student', 'developer', 'superadmin' ) ), username text not null unique, full_name text, display_name text, metadata jsonb default '{}'::jsonb, user_db_name text, school_db_name text, neo4j_sync_status text default 'pending' check (neo4j_sync_status in ('pending', 'ready', 'failed')), neo4j_synced_at timestamp with time zone, last_login timestamp with time zone, created_at timestamp with time zone default timezone('utc'::text, now()), updated_at timestamp with time zone default timezone('utc'::text, now()) ); comment on table public.profiles is 'User profiles linked to Supabase auth.users'; comment on column public.profiles.user_type is 'Type of user: teacher or student'; -- Active institutes create table if not exists public.institutes ( id uuid primary key default uuid_generate_v4(), name text not null, urn text unique, status text not null default 'active' check (status in ('active', 'inactive', 'pending')), address jsonb default '{}'::jsonb, website text, metadata jsonb default '{}'::jsonb, geo_coordinates jsonb default '{}'::jsonb, neo4j_uuid_string text, neo4j_public_sync_status text default 'pending' check (neo4j_public_sync_status in ('pending', 'synced', 'failed')), neo4j_public_sync_at timestamp with time zone, neo4j_private_sync_status text default 'not_started' check (neo4j_private_sync_status in ('not_started', 'pending', 'synced', 'failed')), neo4j_private_sync_at timestamp with time zone, created_at timestamp with time zone default timezone('utc'::text, now()), updated_at timestamp with time zone default timezone('utc'::text, now()) ); comment on table public.institutes is 'Active institutes in the system'; comment on column public.institutes.geo_coordinates is 'Geospatial coordinates from OSM search (latitude, longitude, boundingbox)'; --[ 6. neoFS Filesystem Tables ]-- -- File cabinets for organizing files create table if not exists public.file_cabinets ( id uuid primary key default uuid_generate_v4(), user_id uuid not null references public.profiles(id) on delete cascade, name text not null, created_at timestamp with time zone default timezone('utc'::text, now()) ); comment on table public.file_cabinets is 'User file cabinets for organizing documents and files'; -- Files stored in cabinets create table if not exists public.files ( id uuid primary key default uuid_generate_v4(), cabinet_id uuid not null references public.file_cabinets(id) on delete cascade, name text not null, path text not null, bucket text default 'file-cabinets' not null, created_at timestamp with time zone default timezone('utc'::text, now()), mime_type text, metadata jsonb default '{}'::jsonb, size text, category text generated always as ( case when mime_type like 'image/%' then 'image' when mime_type = 'application/pdf' then 'document' when mime_type in ('application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') then 'document' when mime_type in ('application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') then 'spreadsheet' when mime_type in ('application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation') then 'presentation' when mime_type like 'audio/%' then 'audio' when mime_type like 'video/%' then 'video' else 'other' end ) stored ); comment on table public.files is 'Files stored in user cabinets with automatic categorization'; comment on column public.files.category is 'Automatically determined file category based on MIME type'; -- AI brains for processing files create table if not exists public.brains ( id uuid primary key default uuid_generate_v4(), user_id uuid not null references public.profiles(id) on delete cascade, name text not null, purpose text, created_at timestamp with time zone default timezone('utc'::text, now()) ); comment on table public.brains is 'AI brains for processing and analyzing user files'; -- Brain-file associations create table if not exists public.brain_files ( brain_id uuid not null references public.brains(id) on delete cascade, file_id uuid not null references public.files(id) on delete cascade, primary key (brain_id, file_id) ); comment on table public.brain_files is 'Associations between AI brains and files for processing'; -- Document artefacts from file processing create table if not exists public.document_artefacts ( id uuid primary key default uuid_generate_v4(), file_id uuid references public.files(id) on delete cascade, page_number integer default 0 not null, type text not null, rel_path text not null, size_tag text, language text, chunk_index integer, extra jsonb, created_at timestamp with time zone default timezone('utc'::text, now()), status text default 'completed' not null check (status in ('pending', 'processing', 'completed', 'failed')), started_at timestamp with time zone default timezone('utc'::text, now()), completed_at timestamp with time zone, error_message text ); comment on table public.document_artefacts is 'Extracted artefacts from document processing'; comment on column public.document_artefacts.status is 'Extraction status: pending, processing, completed, or failed'; comment on column public.document_artefacts.started_at is 'Timestamp when extraction process started'; comment on column public.document_artefacts.completed_at is 'Timestamp when extraction process completed (success or failure)'; comment on column public.document_artefacts.error_message is 'Error details if extraction failed'; -- Function execution logs create table if not exists public.function_logs ( id serial primary key, file_id uuid references public.files(id) on delete cascade, timestamp timestamp with time zone default timezone('utc'::text, now()), step text, message text, data jsonb ); comment on table public.function_logs is 'Logs of function executions and processing steps'; --[ 7. Relationship Tables ]-- -- Institute memberships create table if not exists public.institute_memberships ( id uuid primary key default uuid_generate_v4(), profile_id uuid references public.profiles(id) on delete cascade, institute_id uuid references public.institutes(id) on delete cascade, role text not null check (role in ('teacher', 'student')), tldraw_preferences jsonb default '{}'::jsonb, metadata jsonb default '{}'::jsonb, created_at timestamp with time zone default timezone('utc'::text, now()), updated_at timestamp with time zone default timezone('utc'::text, now()), unique(profile_id, institute_id) ); comment on table public.institute_memberships is 'Manages user roles and relationships with institutes'; -- Membership requests create table if not exists public.institute_membership_requests ( id uuid primary key default uuid_generate_v4(), profile_id uuid references public.profiles(id) on delete cascade, institute_id uuid references public.institutes(id) on delete cascade, requested_role text check (requested_role in ('teacher', 'student')), status text default 'pending' check (status in ('pending', 'approved', 'rejected')), metadata jsonb default '{}'::jsonb, created_at timestamp with time zone default timezone('utc'::text, now()), updated_at timestamp with time zone default timezone('utc'::text, now()) ); comment on table public.institute_membership_requests is 'Tracks requests to join institutes'; --[ 8. Audit Tables ]-- -- System audit logs create table if not exists public.audit_logs ( id uuid primary key default uuid_generate_v4(), profile_id uuid references public.profiles(id) on delete set null, action_type text, table_name text, record_id uuid, changes jsonb, created_at timestamp with time zone default timezone('utc'::text, now()) ); comment on table public.audit_logs is 'System-wide audit trail for important operations'; --[ 9. Indexes ]-- -- Document artefacts indexes create index if not exists idx_document_artefacts_file_status on public.document_artefacts(file_id, status); create index if not exists idx_document_artefacts_file_type on public.document_artefacts(file_id, type); create index if not exists idx_document_artefacts_status on public.document_artefacts(status); -- File indexes create index if not exists idx_files_cabinet_id on public.files(cabinet_id); create index if not exists idx_files_mime_type on public.files(mime_type); create index if not exists idx_files_category on public.files(category); -- Brain indexes create index if not exists idx_brains_user_id on public.brains(user_id); --[ 10. Triggers ]-- -- Set completed_at when document artefact status changes to completed create trigger trigger_set_completed_at before update on public.document_artefacts for each row execute function public.set_completed_at(); -- Set updated_at on profile updates create trigger trigger_profiles_updated_at before update on public.profiles for each row execute function public.handle_updated_at(); -- Set updated_at on institute updates create trigger trigger_institutes_updated_at before update on public.institutes for each row execute function public.handle_updated_at(); -- Set updated_at on institute_memberships updates create trigger trigger_institute_memberships_updated_at before update on public.institute_memberships for each row execute function public.handle_updated_at(); -- Set updated_at on institute_membership_requests updates create trigger trigger_institute_membership_requests_updated_at before update on public.institute_memberships for each row execute function public.handle_updated_at(); --[ 11. Additional Indexes ]-- -- Index for geospatial queries create index if not exists idx_institutes_geo_coordinates on public.institutes using gin(geo_coordinates); create index if not exists idx_institutes_urn on public.institutes(urn);