-- ============================================================ -- Fourge Branding Portal — Database Schema v2 -- Paste this entire file into Supabase → SQL Editor → Run -- ============================================================ -- Companies (client companies — created and managed by team) create table public.companies ( id uuid default gen_random_uuid() primary key, name text not null, email text default '', phone text default '', created_at timestamptz default now() not null ); alter table public.companies enable row level security; -- Profiles (extends auth.users — auto-created on signup) create table public.profiles ( id uuid references auth.users on delete cascade primary key, name text not null default '', email text default '', role text not null check (role in ('team', 'client')) default 'client', company_id uuid references public.companies(id) on delete set null, created_at timestamptz default now() not null ); alter table public.profiles enable row level security; -- Projects (belong to a company) create table public.projects ( id uuid default gen_random_uuid() primary key, company_id uuid references public.companies(id) on delete cascade not null, name text not null, description text default '', status text not null check (status in ('active', 'completed')) default 'active', created_at timestamptz default now() not null ); alter table public.projects enable row level security; -- Tasks (belong to a project) create table public.tasks ( id uuid default gen_random_uuid() primary key, project_id uuid references public.projects(id) on delete cascade not null, title text not null, assigned_to uuid references public.profiles(id) on delete set null, assigned_name text, status text not null check (status in ('not_started', 'in_progress', 'on_hold', 'client_review', 'client_approved')) default 'not_started', current_version integer default 0 not null, invoiced boolean default false, submitted_at timestamptz default now() not null, completed_at timestamptz ); alter table public.tasks enable row level security; -- Submissions (each version of a request) create table public.submissions ( id uuid default gen_random_uuid() primary key, task_id uuid references public.tasks(id) on delete cascade not null, version_number integer not null, type text not null check (type in ('initial', 'revision')) default 'initial', service_type text default '', deadline date, description text default '', submitted_by uuid references public.profiles(id) on delete set null, submitted_by_name text default '', submitted_at timestamptz default now() not null ); alter table public.submissions enable row level security; -- Submission Files create table public.submission_files ( id uuid default gen_random_uuid() primary key, submission_id uuid references public.submissions(id) on delete cascade not null, name text not null, storage_path text, size bigint default 0 ); alter table public.submission_files enable row level security; -- Deliveries (work sent to client) create table public.deliveries ( id uuid default gen_random_uuid() primary key, submission_id uuid references public.submissions(id) on delete cascade not null unique, sent_at timestamptz default now() not null, sent_by text default '', message text default '' ); alter table public.deliveries enable row level security; -- Delivery Files create table public.delivery_files ( id uuid default gen_random_uuid() primary key, delivery_id uuid references public.deliveries(id) on delete cascade not null, name text not null, storage_path text, size bigint default 0 ); alter table public.delivery_files enable row level security; -- Company Prices (per service type, per company) create table public.company_prices ( id uuid default gen_random_uuid() primary key, company_id uuid references public.companies(id) on delete cascade not null, service_type text not null, price numeric(10,2) default 0, unique(company_id, service_type) ); alter table public.company_prices enable row level security; -- Invoices create table public.invoices ( id uuid default gen_random_uuid() primary key, company_id uuid references public.companies(id) on delete cascade not null, invoice_number text not null, status text not null check (status in ('draft', 'sent', 'paid')) default 'draft', invoice_date date default current_date, due_date date, total numeric(10,2) default 0, notes text default '', created_by uuid references public.profiles(id) on delete set null, created_at timestamptz default now() not null ); alter table public.invoices enable row level security; -- Invoice Items create table public.invoice_items ( id uuid default gen_random_uuid() primary key, invoice_id uuid references public.invoices(id) on delete cascade not null, task_id uuid references public.tasks(id) on delete set null, description text not null, quantity numeric(10,2) default 1, unit_price numeric(10,2) default 0, created_at timestamptz default now() not null ); alter table public.invoice_items enable row level security; -- ============================================================ -- Helpers -- ============================================================ create or replace function public.get_my_role() returns text as $$ select role from public.profiles where id = auth.uid(); $$ language sql security definer stable; create or replace function public.get_my_company_id() returns uuid as $$ select company_id from public.profiles where id = auth.uid(); $$ language sql security definer stable; -- ============================================================ -- RLS Policies -- ============================================================ -- Companies create policy "Team all companies" on public.companies for all using (get_my_role() = 'team'); create policy "Client reads own company" on public.companies for select using (id = get_my_company_id()); -- Profiles create policy "Own profile select" on public.profiles for select using (id = auth.uid()); create policy "Team reads all profiles" on public.profiles for select using (get_my_role() = 'team'); create policy "Own profile update" on public.profiles for update using (id = auth.uid()); create policy "Team update profiles" on public.profiles for update using (get_my_role() = 'team'); -- Projects create policy "Team all projects" on public.projects for all using (get_my_role() = 'team'); create policy "Client reads company projects" on public.projects for select using (company_id = get_my_company_id()); create policy "Client inserts company projects" on public.projects for insert with check (company_id = get_my_company_id()); -- Tasks create policy "Team all tasks" on public.tasks for all using (get_my_role() = 'team'); create policy "Client reads company tasks" on public.tasks for select using ( project_id in (select id from public.projects where company_id = get_my_company_id()) ); create policy "Client insert task" on public.tasks for insert with check ( project_id in (select id from public.projects where company_id = get_my_company_id()) ); create policy "Client updates company tasks" on public.tasks for update using ( project_id in (select id from public.projects where company_id = get_my_company_id()) ); -- Submissions create policy "Team all submissions" on public.submissions for all using (get_my_role() = 'team'); create policy "Client reads company submissions" on public.submissions for select using ( task_id in ( select t.id from public.tasks t join public.projects p on p.id = t.project_id where p.company_id = get_my_company_id() ) ); create policy "Client inserts submissions" on public.submissions for insert with check (submitted_by = auth.uid()); -- Submission Files create policy "Team all submission_files" on public.submission_files for all using (get_my_role() = 'team'); create policy "Client reads company submission_files" on public.submission_files for select using ( submission_id in ( select s.id from public.submissions s join public.tasks t on t.id = s.task_id join public.projects p on p.id = t.project_id where p.company_id = get_my_company_id() ) ); create policy "Client inserts submission_files" on public.submission_files for insert with check (true); -- Deliveries create policy "Team all deliveries" on public.deliveries for all using (get_my_role() = 'team'); create policy "Client reads company deliveries" on public.deliveries for select using ( submission_id in ( select s.id from public.submissions s join public.tasks t on t.id = s.task_id join public.projects p on p.id = t.project_id where p.company_id = get_my_company_id() ) ); -- Delivery Files create policy "Team all delivery_files" on public.delivery_files for all using (get_my_role() = 'team'); create policy "Client reads company delivery_files" on public.delivery_files for select using ( delivery_id in ( select d.id from public.deliveries d join public.submissions s on s.id = d.submission_id join public.tasks t on t.id = s.task_id join public.projects p on p.id = t.project_id where p.company_id = get_my_company_id() ) ); -- Company Prices create policy "Team all company_prices" on public.company_prices for all using (get_my_role() = 'team'); create policy "Client reads own company prices" on public.company_prices for select using (company_id = get_my_company_id()); -- Invoices create policy "Team all invoices" on public.invoices for all using (get_my_role() = 'team'); create policy "Client reads company invoices" on public.invoices for select using (company_id = get_my_company_id()); -- Invoice Items create policy "Team all invoice_items" on public.invoice_items for all using (get_my_role() = 'team'); create policy "Client reads company invoice_items" on public.invoice_items for select using ( invoice_id in (select id from public.invoices where company_id = get_my_company_id()) ); -- ============================================================ -- Storage Buckets -- ============================================================ insert into storage.buckets (id, name, public) values ('submissions', 'submissions', false); insert into storage.buckets (id, name, public) values ('deliveries', 'deliveries', false); create policy "Auth users upload to submissions" on storage.objects for insert to authenticated with check (bucket_id = 'submissions'); create policy "Auth users read submissions" on storage.objects for select to authenticated using (bucket_id = 'submissions'); create policy "Team upload deliveries" on storage.objects for insert to authenticated with check (bucket_id = 'deliveries' and get_my_role() = 'team'); create policy "Auth users read deliveries" on storage.objects for select to authenticated using (bucket_id = 'deliveries'); -- ============================================================ -- Trigger: auto-create profile on signup -- Team assigns company_id later via the Companies page -- ============================================================ create or replace function public.handle_new_user() returns trigger as $$ begin insert into public.profiles (id, name, email, role) values ( new.id, coalesce(new.raw_user_meta_data->>'name', ''), new.email, coalesce(new.raw_user_meta_data->>'role', 'client') ); return new; end; $$ language plpgsql security definer; create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user();