291 lines
12 KiB
PL/PgSQL
291 lines
12 KiB
PL/PgSQL
--[ 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);
|