feat(P4C.T1): user_soundboards table + soundboards storage bucket + RLS
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
-- Phase 4C: cloud-synced soundboard.
|
||||
--
|
||||
-- user_soundboards: one row per cloud-known sound. The audio payload itself
|
||||
-- lives in the `soundboards` storage bucket at `<user_id>/<sound_id>.bin`,
|
||||
-- E2E-encrypted with the user's own X25519 key (sealed-to-self). The first
|
||||
-- 24 bytes of the stored object are the XSalsa20 nonce; the rest is the
|
||||
-- Poly1305-authenticated ciphertext.
|
||||
--
|
||||
-- Metadata (name, gain, hotkey, category, sort_order) is stored plaintext —
|
||||
-- not considered sensitive by the spec — so other devices of the same user
|
||||
-- can read it without holding the private key.
|
||||
|
||||
create table if not exists public.user_soundboards (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
name text not null,
|
||||
mime text not null,
|
||||
size bigint not null,
|
||||
category text null,
|
||||
hotkey text null,
|
||||
gain real not null default 1.0,
|
||||
sort_order integer not null default 0,
|
||||
storage_path text not null,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint user_soundboards_name_len check (length(name) between 1 and 200),
|
||||
constraint user_soundboards_size_pos check (size > 0)
|
||||
);
|
||||
|
||||
create index if not exists user_soundboards_user_idx
|
||||
on public.user_soundboards(user_id, updated_at desc);
|
||||
|
||||
alter table public.user_soundboards enable row level security;
|
||||
|
||||
drop policy if exists user_soundboards_select on public.user_soundboards;
|
||||
drop policy if exists user_soundboards_insert on public.user_soundboards;
|
||||
drop policy if exists user_soundboards_update on public.user_soundboards;
|
||||
drop policy if exists user_soundboards_delete on public.user_soundboards;
|
||||
|
||||
create policy user_soundboards_select
|
||||
on public.user_soundboards
|
||||
for select
|
||||
using (user_id = auth.uid());
|
||||
|
||||
create policy user_soundboards_insert
|
||||
on public.user_soundboards
|
||||
for insert
|
||||
with check (user_id = auth.uid());
|
||||
|
||||
create policy user_soundboards_update
|
||||
on public.user_soundboards
|
||||
for update
|
||||
using (user_id = auth.uid())
|
||||
with check (user_id = auth.uid());
|
||||
|
||||
create policy user_soundboards_delete
|
||||
on public.user_soundboards
|
||||
for delete
|
||||
using (user_id = auth.uid());
|
||||
|
||||
alter table public.user_soundboards replica identity full;
|
||||
|
||||
do $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1
|
||||
from pg_publication_tables
|
||||
where pubname = 'supabase_realtime'
|
||||
and schemaname = 'public'
|
||||
and tablename = 'user_soundboards'
|
||||
) then
|
||||
execute 'alter publication supabase_realtime add table public.user_soundboards';
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
|
||||
-- Storage bucket: private, owner-only.
|
||||
do $$
|
||||
begin
|
||||
if not exists (select 1 from storage.buckets where id = 'soundboards') then
|
||||
insert into storage.buckets (id, name, public)
|
||||
values ('soundboards', 'soundboards', false);
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
|
||||
drop policy if exists soundboards_select on storage.objects;
|
||||
drop policy if exists soundboards_insert on storage.objects;
|
||||
drop policy if exists soundboards_update on storage.objects;
|
||||
drop policy if exists soundboards_delete on storage.objects;
|
||||
|
||||
create policy soundboards_select
|
||||
on storage.objects
|
||||
for select
|
||||
using (
|
||||
bucket_id = 'soundboards'
|
||||
and auth.uid()::text = (storage.foldername(name))[1]
|
||||
);
|
||||
|
||||
create policy soundboards_insert
|
||||
on storage.objects
|
||||
for insert
|
||||
with check (
|
||||
bucket_id = 'soundboards'
|
||||
and auth.uid()::text = (storage.foldername(name))[1]
|
||||
);
|
||||
|
||||
create policy soundboards_update
|
||||
on storage.objects
|
||||
for update
|
||||
using (
|
||||
bucket_id = 'soundboards'
|
||||
and auth.uid()::text = (storage.foldername(name))[1]
|
||||
)
|
||||
with check (
|
||||
bucket_id = 'soundboards'
|
||||
and auth.uid()::text = (storage.foldername(name))[1]
|
||||
);
|
||||
|
||||
create policy soundboards_delete
|
||||
on storage.objects
|
||||
for delete
|
||||
using (
|
||||
bucket_id = 'soundboards'
|
||||
and auth.uid()::text = (storage.foldername(name))[1]
|
||||
);
|
||||
Reference in New Issue
Block a user