Roadbound-BRR/supabase/migrations/20260325120000_collab_schema.sql

308 lines
9.3 KiB
PL/PgSQL

create extension if not exists pgcrypto;
do $$
begin
if not exists (select 1 from pg_type where typname = 'organization_role') then
create type public.organization_role as enum ('owner', 'admin', 'member');
end if;
if not exists (select 1 from pg_type where typname = 'channel_type') then
create type public.channel_type as enum ('text', 'voice', 'announcement');
end if;
end $$;
create table if not exists public.organizations (
id uuid primary key default gen_random_uuid(),
name text not null,
slug text not null unique,
owner_user_id uuid not null references auth.users(id) on delete restrict,
created_at timestamptz not null default now()
);
create table if not exists public.organization_members (
organization_id uuid not null references public.organizations(id) on delete cascade,
user_id uuid not null references auth.users(id) on delete cascade,
role public.organization_role not null default 'member',
joined_at timestamptz not null default now(),
primary key (organization_id, user_id)
);
create table if not exists public.channels (
id uuid primary key default gen_random_uuid(),
organization_id uuid not null references public.organizations(id) on delete cascade,
name text not null,
slug text not null,
type public.channel_type not null default 'text',
position integer not null default 0,
topic text,
is_private boolean not null default false,
created_by uuid not null references auth.users(id) on delete restrict,
created_at timestamptz not null default now(),
unique (organization_id, slug)
);
create table if not exists public.channel_members (
channel_id uuid not null references public.channels(id) on delete cascade,
user_id uuid not null references auth.users(id) on delete cascade,
joined_at timestamptz not null default now(),
primary key (channel_id, user_id)
);
create table if not exists public.messages (
id uuid primary key default gen_random_uuid(),
channel_id uuid not null references public.channels(id) on delete cascade,
author_user_id uuid not null references auth.users(id) on delete restrict,
content text not null check (char_length(content) <= 4000),
created_at timestamptz not null default now(),
edited_at timestamptz,
deleted_at timestamptz
);
create index if not exists idx_org_members_user
on public.organization_members(user_id);
create index if not exists idx_channels_org_position
on public.channels(organization_id, position);
create index if not exists idx_messages_channel_created_at_desc
on public.messages(channel_id, created_at desc);
create or replace function public.is_org_member(org_id uuid, uid uuid default auth.uid())
returns boolean
language sql
stable
security definer
set search_path = public
as $$
select exists (
select 1
from public.organization_members om
where om.organization_id = org_id
and om.user_id = uid
);
$$;
create or replace function public.org_role(org_id uuid, uid uuid default auth.uid())
returns public.organization_role
language sql
stable
security definer
set search_path = public
as $$
select om.role
from public.organization_members om
where om.organization_id = org_id
and om.user_id = uid
limit 1;
$$;
create or replace function public.can_access_channel(ch_id uuid, uid uuid default auth.uid())
returns boolean
language sql
stable
security definer
set search_path = public
as $$
select exists (
select 1
from public.channels c
where c.id = ch_id
and public.is_org_member(c.organization_id, uid)
and (
c.is_private = false
or exists (
select 1
from public.channel_members cm
where cm.channel_id = c.id
and cm.user_id = uid
)
)
);
$$;
alter table public.organizations enable row level security;
alter table public.organization_members enable row level security;
alter table public.channels enable row level security;
alter table public.channel_members enable row level security;
alter table public.messages enable row level security;
drop policy if exists "organizations_select_members" on public.organizations;
create policy "organizations_select_members"
on public.organizations
for select
to authenticated
using (public.is_org_member(id));
drop policy if exists "organizations_insert_owner" on public.organizations;
create policy "organizations_insert_owner"
on public.organizations
for insert
to authenticated
with check (owner_user_id = auth.uid());
drop policy if exists "organizations_update_admins" on public.organizations;
create policy "organizations_update_admins"
on public.organizations
for update
to authenticated
using (public.org_role(id) in ('owner', 'admin'))
with check (public.org_role(id) in ('owner', 'admin'));
drop policy if exists "organization_members_select_members" on public.organization_members;
create policy "organization_members_select_members"
on public.organization_members
for select
to authenticated
using (public.is_org_member(organization_id));
drop policy if exists "organization_members_insert_admins" on public.organization_members;
create policy "organization_members_insert_admins"
on public.organization_members
for insert
to authenticated
with check (public.org_role(organization_id) in ('owner', 'admin'));
drop policy if exists "organization_members_update_admins" on public.organization_members;
create policy "organization_members_update_admins"
on public.organization_members
for update
to authenticated
using (public.org_role(organization_id) in ('owner', 'admin'))
with check (public.org_role(organization_id) in ('owner', 'admin'));
drop policy if exists "organization_members_delete_admins" on public.organization_members;
create policy "organization_members_delete_admins"
on public.organization_members
for delete
to authenticated
using (public.org_role(organization_id) in ('owner', 'admin'));
drop policy if exists "channels_select_visible" on public.channels;
create policy "channels_select_visible"
on public.channels
for select
to authenticated
using (public.can_access_channel(id));
drop policy if exists "channels_insert_admins" on public.channels;
create policy "channels_insert_admins"
on public.channels
for insert
to authenticated
with check (
public.org_role(organization_id) in ('owner', 'admin')
and created_by = auth.uid()
);
drop policy if exists "channels_update_admins" on public.channels;
create policy "channels_update_admins"
on public.channels
for update
to authenticated
using (public.org_role(organization_id) in ('owner', 'admin'))
with check (public.org_role(organization_id) in ('owner', 'admin'));
drop policy if exists "channels_delete_admins" on public.channels;
create policy "channels_delete_admins"
on public.channels
for delete
to authenticated
using (public.org_role(organization_id) in ('owner', 'admin'));
drop policy if exists "channel_members_select_visible" on public.channel_members;
create policy "channel_members_select_visible"
on public.channel_members
for select
to authenticated
using (
exists (
select 1
from public.channels c
where c.id = channel_id
and public.is_org_member(c.organization_id)
)
);
drop policy if exists "channel_members_insert_admins" on public.channel_members;
create policy "channel_members_insert_admins"
on public.channel_members
for insert
to authenticated
with check (
exists (
select 1
from public.channels c
where c.id = channel_id
and public.org_role(c.organization_id) in ('owner', 'admin')
)
);
drop policy if exists "channel_members_delete_admins" on public.channel_members;
create policy "channel_members_delete_admins"
on public.channel_members
for delete
to authenticated
using (
exists (
select 1
from public.channels c
where c.id = channel_id
and public.org_role(c.organization_id) in ('owner', 'admin')
)
);
drop policy if exists "messages_select_visible_channel" on public.messages;
create policy "messages_select_visible_channel"
on public.messages
for select
to authenticated
using (public.can_access_channel(channel_id));
drop policy if exists "messages_insert_visible_channel" on public.messages;
create policy "messages_insert_visible_channel"
on public.messages
for insert
to authenticated
with check (
public.can_access_channel(channel_id)
and author_user_id = auth.uid()
and deleted_at is null
);
drop policy if exists "messages_update_author_or_admin" on public.messages;
create policy "messages_update_author_or_admin"
on public.messages
for update
to authenticated
using (
author_user_id = auth.uid()
or exists (
select 1
from public.channels c
where c.id = channel_id
and public.org_role(c.organization_id) in ('owner', 'admin')
)
)
with check (
author_user_id = auth.uid()
or exists (
select 1
from public.channels c
where c.id = channel_id
and public.org_role(c.organization_id) in ('owner', 'admin')
)
);
drop policy if exists "messages_delete_author_or_admin" on public.messages;
create policy "messages_delete_author_or_admin"
on public.messages
for delete
to authenticated
using (
author_user_id = auth.uid()
or exists (
select 1
from public.channels c
where c.id = channel_id
and public.org_role(c.organization_id) in ('owner', 'admin')
)
);