eee0885811
- Remove recursive directory size calculations (single Seafile API call per list) - Remove 'Used in this location' usage display - Fix move using v2 per-type endpoints instead of broken batch endpoint - Send entry type from frontend for correct move routing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
655 lines
27 KiB
PL/PgSQL
Executable File
655 lines
27 KiB
PL/PgSQL
Executable File
-- ============================================================
|
|
-- 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,
|
|
phone text default '',
|
|
address text default '',
|
|
contact_name text,
|
|
contact_email text,
|
|
contact_phone text,
|
|
client_logo_url text,
|
|
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', 'external')) 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,
|
|
constraint projects_name_not_blank check (length(trim(name)) > 0),
|
|
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,
|
|
request_key uuid,
|
|
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,
|
|
request_key uuid,
|
|
version_number integer not null,
|
|
type text not null check (type in ('initial', 'revision', 'amendment')) default 'initial',
|
|
revision_type text check (revision_type in ('fourge_error', 'client_revision')),
|
|
invoiced boolean not null default false,
|
|
is_hot boolean not null default false,
|
|
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;
|
|
|
|
create unique index if not exists tasks_request_key_key
|
|
on public.tasks (request_key)
|
|
where request_key is not null;
|
|
|
|
create unique index if not exists submissions_request_key_key
|
|
on public.submissions (request_key)
|
|
where request_key is not null;
|
|
|
|
create unique index if not exists projects_company_normalized_name_key
|
|
on public.projects (company_id, lower(btrim(name)));
|
|
|
|
-- 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;
|
|
|
|
-- Project Members (external users assigned to specific projects)
|
|
create table public.project_members (
|
|
id uuid default gen_random_uuid() primary key,
|
|
project_id uuid references public.projects(id) on delete cascade not null,
|
|
profile_id uuid references public.profiles(id) on delete cascade not null,
|
|
created_at timestamptz default now() not null,
|
|
unique(project_id, profile_id)
|
|
);
|
|
alter table public.project_members enable row level security;
|
|
|
|
-- Company Prices (per service type, per company, per price type)
|
|
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,
|
|
price_type text not null default 'new' check (price_type in ('new', 'revision')),
|
|
unique(company_id, service_type, price_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 '',
|
|
bill_to text,
|
|
invoice_email text,
|
|
stripe_fee numeric(10,2),
|
|
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;
|
|
|
|
-- Expenses
|
|
create table public.expenses (
|
|
id uuid default gen_random_uuid() primary key,
|
|
date date default current_date not null,
|
|
description text not null,
|
|
category text not null default 'Other',
|
|
amount numeric(10,2) not null,
|
|
notes text default '',
|
|
receipt_path text,
|
|
receipt_name text,
|
|
created_by uuid references public.profiles(id) on delete set null,
|
|
created_at timestamptz default now() not null
|
|
);
|
|
alter table public.expenses enable row level security;
|
|
create policy "Team all expenses" on public.expenses for all using (get_my_role() = 'team');
|
|
|
|
-- Subcontractor Payments (tracked separately, included as contractor expenses)
|
|
create table public.subcontractor_payments (
|
|
id uuid default gen_random_uuid() primary key,
|
|
profile_id uuid references public.profiles(id) on delete set null,
|
|
date date default current_date not null,
|
|
description text not null,
|
|
amount numeric(10,2) not null,
|
|
status text not null default 'pending' check (status in ('pending', 'paid')),
|
|
paid_at date,
|
|
notes text default '',
|
|
created_by uuid references public.profiles(id) on delete set null,
|
|
created_at timestamptz default now() not null
|
|
);
|
|
alter table public.subcontractor_payments enable row level security;
|
|
create policy "Team all subcontractor_payments" on public.subcontractor_payments
|
|
for all using (get_my_role() = 'team') with check (get_my_role() = 'team');
|
|
|
|
-- Meeting Notes
|
|
create table public.meeting_notes (
|
|
id uuid default gen_random_uuid() primary key,
|
|
meeting_at timestamptz default now() not null,
|
|
title text not null,
|
|
attendees text default '',
|
|
notes text not null default '',
|
|
created_by uuid references public.profiles(id) on delete set null,
|
|
created_at timestamptz default now() not null,
|
|
updated_at timestamptz default now() not null
|
|
);
|
|
alter table public.meeting_notes enable row level security;
|
|
create policy "Team all meeting_notes" on public.meeting_notes
|
|
for all using (get_my_role() = 'team') with check (get_my_role() = 'team');
|
|
|
|
-- 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,
|
|
submission_id uuid references public.submissions(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;
|
|
|
|
-- Brand Books (sign survey / brand book records, team only)
|
|
create table public.brand_books (
|
|
id uuid default gen_random_uuid() primary key,
|
|
client_id uuid references public.companies(id) on delete set null,
|
|
client_name text not null default '',
|
|
project_name text default '',
|
|
site_address text default '',
|
|
book_date date,
|
|
prepared_by text default '',
|
|
revision text default '',
|
|
template text default 'fourge',
|
|
site_map_path text,
|
|
inventory_map_path text,
|
|
signs jsonb default '[]',
|
|
survey_photo_paths text[] default '{}',
|
|
updated_at timestamptz default now() not null,
|
|
-- Cover page fields
|
|
project_logo_path text,
|
|
creation_date date,
|
|
revision_date date,
|
|
customer_name text,
|
|
customer_address text,
|
|
client_logo_url text,
|
|
client_contact_name text,
|
|
client_contact_email text,
|
|
client_contact_phone text,
|
|
approved_date date,
|
|
approval_notes text
|
|
);
|
|
alter table public.brand_books enable row level security;
|
|
|
|
-- Server Status Overrides (team-managed usage inputs for metrics without live APIs)
|
|
create table public.server_status_overrides (
|
|
id boolean primary key default true,
|
|
supabase_egress_bytes bigint,
|
|
vercel_fast_data_transfer_bytes bigint,
|
|
vercel_edge_requests bigint,
|
|
vercel_function_invocations bigint,
|
|
vercel_active_cpu_hours numeric(10,4),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
alter table public.server_status_overrides enable row level security;
|
|
|
|
create table public.fourge_passwords (
|
|
id uuid default gen_random_uuid() primary key,
|
|
service_name text not null,
|
|
service_url text default '',
|
|
username text not null default '',
|
|
encrypted_password text not null,
|
|
password_iv text not null,
|
|
notes text default '',
|
|
created_by uuid references public.profiles(id) on delete set null,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
alter table public.fourge_passwords 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.is_external()
|
|
returns boolean as $$
|
|
select get_my_role() = 'external';
|
|
$$ 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;
|
|
|
|
create or replace function public.get_database_size_bytes()
|
|
returns bigint as $$
|
|
select pg_database_size(current_database());
|
|
$$ language sql security definer stable;
|
|
|
|
-- Prevents clients and externals from modifying protected task fields.
|
|
-- Fires before UPDATE so WITH CHECK sees the already-corrected new row.
|
|
create or replace function public.guard_task_update()
|
|
returns trigger as $$
|
|
declare
|
|
caller_role text;
|
|
begin
|
|
select role into caller_role from public.profiles where id = auth.uid();
|
|
|
|
if caller_role = 'client' then
|
|
new.project_id := old.project_id;
|
|
new.invoiced := old.invoiced;
|
|
|
|
if not (
|
|
new.status = 'not_started'
|
|
and coalesce(new.current_version, 0) > coalesce(old.current_version, 0)
|
|
and new.assigned_to is null
|
|
and new.assigned_name is null
|
|
) then
|
|
new.assigned_to := old.assigned_to;
|
|
new.assigned_name := old.assigned_name;
|
|
end if;
|
|
elsif caller_role = 'external' then
|
|
new.project_id := old.project_id;
|
|
new.invoiced := old.invoiced;
|
|
end if;
|
|
|
|
return new;
|
|
end;
|
|
$$ language plpgsql security definer;
|
|
|
|
create trigger guard_task_update
|
|
before update on public.tasks
|
|
for each row execute function public.guard_task_update();
|
|
|
|
-- ============================================================
|
|
-- 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());
|
|
create policy "Client updates own company" on public.companies
|
|
for update using (id = get_my_company_id()) with check (id = get_my_company_id());
|
|
|
|
-- Project Members
|
|
create policy "Team all project_members" on public.project_members for all using (get_my_role() = 'team');
|
|
create policy "External reads own memberships" on public.project_members for select using (profile_id = auth.uid());
|
|
|
|
-- 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())
|
|
with check (
|
|
id = auth.uid()
|
|
and role = (select role from public.profiles where id = auth.uid())
|
|
and company_id is not distinct from (select company_id from public.profiles where 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());
|
|
create policy "Client updates own company projects" on public.projects
|
|
for update using (company_id = get_my_company_id()) with check (company_id = get_my_company_id());
|
|
create policy "External reads assigned projects" on public.projects for select using (
|
|
get_my_role() = 'external' and
|
|
id in (select project_id from public.project_members where profile_id = auth.uid())
|
|
);
|
|
|
|
-- 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 (
|
|
get_my_role() = 'client'
|
|
and project_id in (select id from public.projects where company_id = get_my_company_id())
|
|
)
|
|
with check (
|
|
get_my_role() = 'client'
|
|
and project_id in (select id from public.projects where company_id = get_my_company_id())
|
|
);
|
|
create policy "External reads assigned tasks" on public.tasks for select using (
|
|
get_my_role() = 'external' and
|
|
project_id in (select project_id from public.project_members where profile_id = auth.uid())
|
|
);
|
|
create policy "External updates assigned tasks" on public.tasks
|
|
for update
|
|
using (
|
|
get_my_role() = 'external'
|
|
and project_id in (select project_id from public.project_members where profile_id = auth.uid())
|
|
)
|
|
with check (
|
|
get_my_role() = 'external'
|
|
and project_id in (select project_id from public.project_members where profile_id = auth.uid())
|
|
);
|
|
|
|
-- 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 (
|
|
get_my_role() = 'client'
|
|
and submitted_by = auth.uid()
|
|
and 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 "External reads assigned submissions" on public.submissions for select using (
|
|
get_my_role() = 'external' and
|
|
task_id in (
|
|
select t.id from public.tasks t
|
|
join public.project_members pm on pm.project_id = t.project_id
|
|
where pm.profile_id = auth.uid()
|
|
)
|
|
);
|
|
create policy "External inserts submissions" on public.submissions for insert with check (
|
|
get_my_role() = 'external'
|
|
and submitted_by = auth.uid()
|
|
and task_id in (
|
|
select t.id from public.tasks t
|
|
join public.project_members pm on pm.project_id = t.project_id
|
|
where pm.profile_id = 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 (
|
|
get_my_role() = 'client'
|
|
and 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()
|
|
and s.submitted_by = auth.uid()
|
|
)
|
|
);
|
|
create policy "External reads assigned submission_files" on public.submission_files for select using (
|
|
get_my_role() = 'external' and
|
|
submission_id in (
|
|
select s.id from public.submissions s
|
|
join public.tasks t on t.id = s.task_id
|
|
join public.project_members pm on pm.project_id = t.project_id
|
|
where pm.profile_id = auth.uid()
|
|
)
|
|
);
|
|
create policy "External inserts submission_files" on public.submission_files for insert with check (
|
|
get_my_role() = 'external'
|
|
and submission_id in (
|
|
select s.id from public.submissions s
|
|
join public.tasks t on t.id = s.task_id
|
|
join public.project_members pm on pm.project_id = t.project_id
|
|
where pm.profile_id = auth.uid()
|
|
and s.submitted_by = auth.uid()
|
|
)
|
|
);
|
|
|
|
-- 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()
|
|
)
|
|
);
|
|
create policy "External reads assigned deliveries" on public.deliveries for select using (
|
|
get_my_role() = 'external' and
|
|
submission_id in (
|
|
select s.id from public.submissions s
|
|
join public.tasks t on t.id = s.task_id
|
|
join public.project_members pm on pm.project_id = t.project_id
|
|
where pm.profile_id = auth.uid()
|
|
)
|
|
);
|
|
|
|
-- 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()
|
|
)
|
|
);
|
|
create policy "External reads assigned delivery_files" on public.delivery_files for select using (
|
|
get_my_role() = 'external' and
|
|
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.project_members pm on pm.project_id = t.project_id
|
|
where pm.profile_id = auth.uid()
|
|
)
|
|
);
|
|
|
|
-- 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())
|
|
);
|
|
|
|
-- Brand Books
|
|
create policy "Team all brand_books" on public.brand_books for all using (get_my_role() = 'team');
|
|
create policy "Team all server_status_overrides" on public.server_status_overrides for all using (get_my_role() = 'team') with check (get_my_role() = 'team');
|
|
create policy "Team all fourge_passwords" on public.fourge_passwords for all using (get_my_role() = 'team') with check (get_my_role() = 'team');
|
|
|
|
-- ============================================================
|
|
-- Storage Buckets
|
|
-- ============================================================
|
|
insert into storage.buckets (id, name, public) values ('submissions', 'submissions', false);
|
|
insert into storage.buckets (id, name, public) values ('deliveries', 'deliveries', false);
|
|
insert into storage.buckets (id, name, public) values ('company-logos', 'company-logos', true);
|
|
insert into storage.buckets (id, name, public) values ('fourge-files', 'fourge-files', false);
|
|
insert into storage.buckets (id, name, public) values ('expense-receipts', 'expense-receipts', false);
|
|
|
|
-- Company Logos (public bucket — team manages, public can read for embedded URLs in PDFs)
|
|
create policy "Team manages company logos" on storage.objects
|
|
for all to authenticated
|
|
using (bucket_id = 'company-logos' and get_my_role() = 'team')
|
|
with check (bucket_id = 'company-logos' and get_my_role() = 'team');
|
|
create policy "Public can read company logos" on storage.objects
|
|
for select using (bucket_id = 'company-logos');
|
|
|
|
-- Submissions: SELECT
|
|
create policy "Team reads submissions storage" on storage.objects
|
|
for select to authenticated using (bucket_id = 'submissions' and get_my_role() = 'team');
|
|
create policy "Client reads submissions storage" on storage.objects
|
|
for select to authenticated using (
|
|
bucket_id = 'submissions' and get_my_role() = 'client'
|
|
and split_part(name, '/', 1) in (select t.id::text from public.tasks t join public.projects p on p.id = t.project_id where p.company_id = get_my_company_id())
|
|
);
|
|
create policy "External reads submissions storage" on storage.objects
|
|
for select to authenticated using (
|
|
bucket_id = 'submissions' and get_my_role() = 'external'
|
|
and split_part(name, '/', 1) in (select t.id::text from public.tasks t join public.project_members pm on pm.project_id = t.project_id where pm.profile_id = auth.uid())
|
|
);
|
|
|
|
-- Submissions: INSERT
|
|
create policy "Team inserts submissions storage" on storage.objects
|
|
for insert to authenticated with check (bucket_id = 'submissions' and get_my_role() = 'team');
|
|
create policy "Client inserts submissions storage" on storage.objects
|
|
for insert to authenticated with check (
|
|
bucket_id = 'submissions' and get_my_role() = 'client'
|
|
and split_part(name, '/', 1) in (select t.id::text from public.tasks t join public.projects p on p.id = t.project_id where p.company_id = get_my_company_id())
|
|
);
|
|
create policy "External inserts submissions storage" on storage.objects
|
|
for insert to authenticated with check (
|
|
bucket_id = 'submissions' and get_my_role() = 'external'
|
|
and split_part(name, '/', 1) in (select t.id::text from public.tasks t join public.project_members pm on pm.project_id = t.project_id where pm.profile_id = auth.uid())
|
|
);
|
|
|
|
-- Submissions: DELETE (team only)
|
|
create policy "Team deletes submissions storage" on storage.objects
|
|
for delete to authenticated using (bucket_id = 'submissions' and get_my_role() = 'team');
|
|
|
|
-- Deliveries: SELECT
|
|
create policy "Team reads deliveries storage" on storage.objects
|
|
for select to authenticated using (bucket_id = 'deliveries' and get_my_role() = 'team');
|
|
create policy "Client reads deliveries storage" on storage.objects
|
|
for select to authenticated using (
|
|
bucket_id = 'deliveries' and get_my_role() = 'client'
|
|
and split_part(name, '/', 1) in (select t.id::text from public.tasks t join public.projects p on p.id = t.project_id where p.company_id = get_my_company_id())
|
|
);
|
|
create policy "External reads deliveries storage" on storage.objects
|
|
for select to authenticated using (
|
|
bucket_id = 'deliveries' and get_my_role() = 'external'
|
|
and split_part(name, '/', 1) in (select t.id::text from public.tasks t join public.project_members pm on pm.project_id = t.project_id where pm.profile_id = auth.uid())
|
|
);
|
|
|
|
-- Deliveries: INSERT + DELETE (team only)
|
|
create policy "Team inserts deliveries storage" on storage.objects
|
|
for insert to authenticated with check (bucket_id = 'deliveries' and get_my_role() = 'team');
|
|
create policy "Team deletes deliveries storage" on storage.objects
|
|
for delete to authenticated using (bucket_id = 'deliveries' and get_my_role() = 'team');
|
|
|
|
-- Fourge Files: internal team-only company documents
|
|
create policy "Team reads fourge files storage" on storage.objects
|
|
for select to authenticated using (bucket_id = 'fourge-files' and get_my_role() = 'team');
|
|
create policy "Team inserts fourge files storage" on storage.objects
|
|
for insert to authenticated with check (bucket_id = 'fourge-files' and get_my_role() = 'team');
|
|
create policy "Team updates fourge files storage" on storage.objects
|
|
for update to authenticated using (bucket_id = 'fourge-files' and get_my_role() = 'team')
|
|
with check (bucket_id = 'fourge-files' and get_my_role() = 'team');
|
|
create policy "Team deletes fourge files storage" on storage.objects
|
|
for delete to authenticated using (bucket_id = 'fourge-files' and get_my_role() = 'team');
|
|
|
|
-- Expense Receipts: team-only storage for uploaded expense receipts/photos
|
|
create policy "Team reads expense receipts storage" on storage.objects
|
|
for select to authenticated using (bucket_id = 'expense-receipts' and get_my_role() = 'team');
|
|
create policy "Team inserts expense receipts storage" on storage.objects
|
|
for insert to authenticated with check (bucket_id = 'expense-receipts' and get_my_role() = 'team');
|
|
create policy "Team updates expense receipts storage" on storage.objects
|
|
for update to authenticated using (bucket_id = 'expense-receipts' and get_my_role() = 'team')
|
|
with check (bucket_id = 'expense-receipts' and get_my_role() = 'team');
|
|
create policy "Team deletes expense receipts storage" on storage.objects
|
|
for delete to authenticated using (bucket_id = 'expense-receipts' and get_my_role() = 'team');
|
|
|
|
-- ============================================================
|
|
-- 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();
|