chatbot-1 / supabase_schema.sql
stevafernandes's picture
Upload 5 files
f036e7a verified
Raw
History Blame Contribute Delete
5.68 kB
-- ============================================================================
-- MyeAI (Myeloma AI) — Supabase schema
--
-- Run this ONCE in your Supabase project:
-- Supabase Studio ▸ SQL Editor ▸ New query ▸ paste ▸ Run
--
-- It is idempotent: running it again is harmless.
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 1. Resumable session state — one row per participant email.
-- This is what gets loaded when a returning participant signs back in.
-- ----------------------------------------------------------------------------
create table if not exists public.p3_sessions (
email text primary key,
-- The participant's study User ID, entered alongside the email at sign-in.
-- Checked on every return visit: a mismatch is refused rather than letting
-- one participant open another's session by guessing an email address.
user_id text,
-- The 16 intake answers, in question order: ["Yes","No",...]
answers jsonb not null default '[]'::jsonb,
-- Full model-facing conversation, including the hidden profile seed turn.
convo jsonb not null default '[]'::jsonb,
-- What the participant actually sees in the chat window.
chat_display jsonb not null default '[]'::jsonb,
-- Follow-up counter, so the MAX_FOLLOWUPS cap survives a reload.
followups integer not null default 0,
-- Bumped on every conversation write. Used for optimistic locking so a
-- stale tab or a second device cannot overwrite newer state.
revision integer not null default 0,
-- How many of the 16 questions `answers` actually has filled in. The
-- questionnaire auto-saves on every click, and those writes can arrive out
-- of order, so this is used to refuse any write that would replace a more
-- complete set of answers with a staler, emptier one.
answers_count integer not null default 0,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- For projects created before these columns existed.
alter table public.p3_sessions
add column if not exists revision integer not null default 0;
alter table public.p3_sessions
add column if not exists answers_count integer not null default 0;
alter table public.p3_sessions
add column if not exists user_id text;
create index if not exists p3_sessions_user_id_idx
on public.p3_sessions (user_id);
-- ----------------------------------------------------------------------------
-- 2. Append-only log of every chat turn.
-- The session row above is overwritten when a participant starts over;
-- this table is never overwritten, so no transcript is ever lost.
-- ----------------------------------------------------------------------------
create table if not exists public.p3_messages (
id bigint generated always as identity primary key,
email text not null,
role text not null check (role in ('user', 'assistant')),
content text not null,
turn integer,
created_at timestamptz not null default now()
);
create index if not exists p3_messages_email_created_idx
on public.p3_messages (email, created_at);
-- ----------------------------------------------------------------------------
-- 3. Row Level Security.
-- RLS is ON with NO policies, which means the public `anon` key can read
-- and write NOTHING. The app authenticates with the `service_role` key,
-- which bypasses RLS and never leaves the Space's server-side process.
-- (Gradio runs your Python on the server; the key is never sent to a browser.)
-- ----------------------------------------------------------------------------
alter table public.p3_sessions enable row level security;
alter table public.p3_messages enable row level security;
-- Belt and braces: make sure the anon/authenticated roles hold no grants on
-- these tables even if RLS were later disabled by accident.
revoke all on public.p3_sessions from anon, authenticated;
revoke all on public.p3_messages from anon, authenticated;
-- ----------------------------------------------------------------------------
-- 4. Handy views for reviewing collected data in the Studio Table Editor.
-- ----------------------------------------------------------------------------
-- SECURITY: `security_invoker = on` is load-bearing. A plain Postgres view runs
-- with its OWNER's privileges, which would bypass the RLS above and hand every
-- participant's email to anyone holding the public anon key. With
-- security_invoker the view is evaluated as the caller, so RLS still applies.
-- Dropped first because `create or replace view` cannot change the column list
-- of an existing view. The view holds no data, so this is always safe.
drop view if exists public.p3_session_overview;
create view public.p3_session_overview
with (security_invoker = on) as
select
s.user_id,
s.email,
jsonb_array_length(s.answers) as answers_saved,
jsonb_array_length(s.chat_display) as visible_messages,
s.followups,
s.revision,
s.created_at,
s.updated_at,
(select count(*) from public.p3_messages m where m.email = s.email)
as logged_turns
from public.p3_sessions s
order by s.updated_at desc;
-- Supabase grants SELECT on new objects in `public` to anon/authenticated via
-- ALTER DEFAULT PRIVILEGES, so the view must be revoked explicitly too.
revoke all on public.p3_session_overview from anon, authenticated;