Clete Access Hub
Setup Guide
1. Create Supabase Project
2. Add Environment Variables
NEXT_PUBLIC_SUPABASE_URL=... NEXT_PUBLIC_SUPABASE_ANON_KEY=... SUPABASE_SERVICE_ROLE_KEY=... NEXT_PUBLIC_AGENCY_EMAIL=clete@youragency.com NEXT_PUBLIC_META_PARTNER_BUSINESS_ID=... META_APP_ID=... META_APP_SECRET=... META_BUSINESS_LOGIN_CONFIG_ID=... META_GRAPH_VERSION=v24.0 GOOGLE_OAUTH_CLIENT_ID=... GOOGLE_OAUTH_CLIENT_SECRET=...
3. Run SQL
create extension if not exists pgcrypto;
create table if not exists public.clients (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null references auth.users(id) on delete cascade,
name text not null,
package_key text not null,
created_at timestamptz not null default now()
);
create table if not exists public.onboarding_links (
id uuid primary key default gen_random_uuid(),
client_id uuid not null references public.clients(id) on delete cascade,
token text not null unique,
is_active boolean not null default true,
created_at timestamptz not null default now()
);
create table if not exists public.client_tasks (
id uuid primary key default gen_random_uuid(),
client_id uuid not null references public.clients(id) on delete cascade,
task_key text not null,
task_type text not null check (task_type in ('access', 'intake')),
title text not null,
category text not null,
why text not null,
status text not null default 'not_started' check (status in ('not_started', 'waiting', 'submitted', 'confirmed', 'blocked', 'setup_needed')),
metadata jsonb not null default '{}'::jsonb,
response text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists public.task_files (
id uuid primary key default gen_random_uuid(),
task_id uuid not null references public.client_tasks(id) on delete cascade,
client_id uuid not null references public.clients(id) on delete cascade,
file_name text not null,
file_path text not null,
file_size bigint,
mime_type text,
created_at timestamptz not null default now()
);
create index if not exists clients_owner_id_idx on public.clients(owner_id);
create index if not exists onboarding_links_token_idx on public.onboarding_links(token);
create index if not exists onboarding_links_client_id_idx on public.onboarding_links(client_id);
create index if not exists client_tasks_client_id_idx on public.client_tasks(client_id);
create index if not exists task_files_client_id_idx on public.task_files(client_id);
create index if not exists task_files_task_id_idx on public.task_files(task_id);
grant usage on schema public to authenticated;
grant usage on schema public to service_role;
grant select, insert, update, delete on public.clients to authenticated;
grant select, insert, update, delete on public.onboarding_links to authenticated;
grant select, insert, update, delete on public.client_tasks to authenticated;
grant select on public.task_files to authenticated;
grant select, insert, update, delete on public.clients to service_role;
grant select, insert, update, delete on public.onboarding_links to service_role;
grant select, insert, update, delete on public.client_tasks to service_role;
grant select, insert, update, delete on public.task_files to service_role;
alter table public.clients enable row level security;
alter table public.onboarding_links enable row level security;
alter table public.client_tasks enable row level security;
alter table public.task_files enable row level security;
drop policy if exists "Owners can manage their clients" on public.clients;
create policy "Owners can manage their clients"
on public.clients
for all
using (auth.uid() = owner_id)
with check (auth.uid() = owner_id);
drop policy if exists "Owners can manage onboarding links" on public.onboarding_links;
create policy "Owners can manage onboarding links"
on public.onboarding_links
for all
using (
exists (
select 1 from public.clients
where clients.id = onboarding_links.client_id
and clients.owner_id = auth.uid()
)
)
with check (
exists (
select 1 from public.clients
where clients.id = onboarding_links.client_id
and clients.owner_id = auth.uid()
)
);
drop policy if exists "Owners can manage client tasks" on public.client_tasks;
create policy "Owners can manage client tasks"
on public.client_tasks
for all
using (
exists (
select 1 from public.clients
where clients.id = client_tasks.client_id
and clients.owner_id = auth.uid()
)
)
with check (
exists (
select 1 from public.clients
where clients.id = client_tasks.client_id
and clients.owner_id = auth.uid()
)
);
drop policy if exists "Owners can view task files" on public.task_files;
create policy "Owners can view task files"
on public.task_files
for select
using (
exists (
select 1 from public.clients
where clients.id = task_files.client_id
and clients.owner_id = auth.uid()
)
);
-- Client-facing onboarding access is handled by Next.js API routes using the
-- Supabase service role key. Do not expose SUPABASE_SERVICE_ROLE_KEY to the browser.
insert into storage.buckets (id, name, public)
values ('client-uploads', 'client-uploads', false)
on conflict (id) do nothing;