feat: add backend pricing calculation RPC and refactor create_lead function
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -22,6 +22,7 @@ services:
|
|||||||
- ./supabase/migrations/05-create-lead-rpc.sql:/sql/05-create-lead-rpc.sql:ro
|
- ./supabase/migrations/05-create-lead-rpc.sql:/sql/05-create-lead-rpc.sql:ro
|
||||||
- ./supabase/migrations/06-admin-pricing-documents.sql:/sql/06-admin-pricing-documents.sql:ro
|
- ./supabase/migrations/06-admin-pricing-documents.sql:/sql/06-admin-pricing-documents.sql:ro
|
||||||
- ./supabase/migrations/07-sales-orders.sql:/sql/07-sales-orders.sql:ro
|
- ./supabase/migrations/07-sales-orders.sql:/sql/07-sales-orders.sql:ro
|
||||||
|
- ./supabase/migrations/08-backend-pricing-and-security.sql:/sql/08-backend-pricing-and-security.sql:ro
|
||||||
|
|
||||||
kong:
|
kong:
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ services:
|
|||||||
- /mnt/user/appdata/mc-cars/supabase/migrations/05-create-lead-rpc.sql:/sql/05-create-lead-rpc.sql:ro
|
- /mnt/user/appdata/mc-cars/supabase/migrations/05-create-lead-rpc.sql:/sql/05-create-lead-rpc.sql:ro
|
||||||
- /mnt/user/appdata/mc-cars/supabase/migrations/06-admin-pricing-documents.sql:/sql/06-admin-pricing-documents.sql:ro
|
- /mnt/user/appdata/mc-cars/supabase/migrations/06-admin-pricing-documents.sql:/sql/06-admin-pricing-documents.sql:ro
|
||||||
- /mnt/user/appdata/mc-cars/supabase/migrations/07-sales-orders.sql:/sql/07-sales-orders.sql:ro
|
- /mnt/user/appdata/mc-cars/supabase/migrations/07-sales-orders.sql:/sql/07-sales-orders.sql:ro
|
||||||
|
- /mnt/user/appdata/mc-cars/supabase/migrations/08-backend-pricing-and-security.sql:/sql/08-backend-pricing-and-security.sql:ro
|
||||||
entrypoint: ["sh","-c"]
|
entrypoint: ["sh","-c"]
|
||||||
command:
|
command:
|
||||||
- |
|
- |
|
||||||
@@ -238,6 +239,7 @@ services:
|
|||||||
psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/05-create-lead-rpc.sql
|
psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/05-create-lead-rpc.sql
|
||||||
psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/06-admin-pricing-documents.sql
|
psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/06-admin-pricing-documents.sql
|
||||||
psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/07-sales-orders.sql
|
psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/07-sales-orders.sql
|
||||||
|
psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/08-backend-pricing-and-security.sql
|
||||||
echo "post-init done."
|
echo "post-init done."
|
||||||
restart: "no"
|
restart: "no"
|
||||||
networks: [mccars]
|
networks: [mccars]
|
||||||
|
|||||||
+23
-40
@@ -351,7 +351,7 @@ function calcWeekendDays(from, to) {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSidebar() {
|
async function updateSidebar() {
|
||||||
const v = state.vehicles.find(x => x.id === bpfCar.value);
|
const v = state.vehicles.find(x => x.id === bpfCar.value);
|
||||||
const { from, to } = getBpfDates();
|
const { from, to } = getBpfDates();
|
||||||
if (!v || !from || !to) {
|
if (!v || !from || !to) {
|
||||||
@@ -364,18 +364,25 @@ function updateSidebar() {
|
|||||||
if (!fromD || !toD) return;
|
if (!fromD || !toD) return;
|
||||||
if (toD <= fromD) return;
|
if (toD <= fromD) return;
|
||||||
|
|
||||||
const totalDays = Math.ceil((toD - fromD) / (1000 * 60 * 60 * 24));
|
// Fetch price from backend RPC
|
||||||
const weekendDays = bpfDurationMode === "weekend" ? 2 : calcWeekendDays(from, to);
|
const { data: price, error } = await supabase.rpc("calculate_price", {
|
||||||
const weekdays = bpfDurationMode === "weekend" ? 0 : (totalDays - weekendDays);
|
p_vehicle_id: v.id,
|
||||||
|
p_date_from: from,
|
||||||
|
p_date_to: to,
|
||||||
|
});
|
||||||
|
if (error || !price) { console.error("calculate_price error:", error, "data:", price); return; }
|
||||||
|
|
||||||
const weekdayCost = weekdays * v.daily_price_eur;
|
const totalDays = price.total_days;
|
||||||
const weekendCost = weekendDays * (v.weekend_price_eur || v.daily_price_eur);
|
const weekdays = price.weekday_count;
|
||||||
const subtotal = weekdayCost + weekendCost;
|
const weekendDays = price.weekend_day_count;
|
||||||
const vat = Math.round(subtotal * 0.20);
|
const weekdayCost = price.daily_subtotal;
|
||||||
const total = subtotal + vat;
|
const weekendCost = price.weekend_subtotal;
|
||||||
const deposit = v.kaution_eur || 5000;
|
const subtotal = price.subtotal_eur;
|
||||||
const kmPerWeekendDay = v.max_km_weekend || v.max_daily_km || 150;
|
const vat = price.vat_eur;
|
||||||
const kmPerWeekday = v.max_daily_km || 150;
|
const total = price.total_eur;
|
||||||
|
const deposit = price.deposit_eur;
|
||||||
|
const kmPerWeekday = price.max_daily_km;
|
||||||
|
const kmPerWeekendDay = price.max_km_weekend;
|
||||||
const includedKm = (weekdays * kmPerWeekday) + (weekendDays * kmPerWeekendDay);
|
const includedKm = (weekdays * kmPerWeekday) + (weekendDays * kmPerWeekendDay);
|
||||||
|
|
||||||
bpfSidebarPlaceholder.style.display = "none";
|
bpfSidebarPlaceholder.style.display = "none";
|
||||||
@@ -383,8 +390,8 @@ function updateSidebar() {
|
|||||||
bpfSidebarContent.innerHTML = `
|
bpfSidebarContent.innerHTML = `
|
||||||
<h4>${t("bpfPriceOverview")}</h4>
|
<h4>${t("bpfPriceOverview")}</h4>
|
||||||
<div class="bpf-price-row"><span>${v.brand} ${v.model} · ${totalDays} ${t("bpfDays")}</span></div>
|
<div class="bpf-price-row"><span>${v.brand} ${v.model} · ${totalDays} ${t("bpfDays")}</span></div>
|
||||||
${weekdays > 0 ? `<div class="bpf-price-row"><span>${t("bpfWeekdays")} (${weekdays} × € ${v.daily_price_eur})</span><span>€ ${weekdayCost.toLocaleString("de-DE")}</span></div>` : ""}
|
${weekdays > 0 ? `<div class="bpf-price-row"><span>${t("bpfWeekdays")} (${weekdays} × € ${price.daily_price_eur})</span><span>€ ${weekdayCost.toLocaleString("de-DE")}</span></div>` : ""}
|
||||||
${weekendDays > 0 ? `<div class="bpf-price-row"><span>${t("bpfWeekendDays")} (${weekendDays} × € ${v.weekend_price_eur || v.daily_price_eur})</span><span>€ ${weekendCost.toLocaleString("de-DE")}</span></div>` : ""}
|
${weekendDays > 0 ? `<div class="bpf-price-row"><span>${t("bpfWeekendDays")} (${weekendDays} × € ${price.weekend_price_eur})</span><span>€ ${weekendCost.toLocaleString("de-DE")}</span></div>` : ""}
|
||||||
<div class="bpf-price-row"><span>${t("bpfSubtotal")}</span><span>€ ${subtotal.toLocaleString("de-DE")}</span></div>
|
<div class="bpf-price-row"><span>${t("bpfSubtotal")}</span><span>€ ${subtotal.toLocaleString("de-DE")}</span></div>
|
||||||
<div class="bpf-price-row muted"><span>${t("bpfVat")}</span><span>€ ${vat.toLocaleString("de-DE")}</span></div>
|
<div class="bpf-price-row muted"><span>${t("bpfVat")}</span><span>€ ${vat.toLocaleString("de-DE")}</span></div>
|
||||||
<div class="bpf-price-row total"><span>${t("bpfTotal")}</span><span>€ ${total.toLocaleString("de-DE")}</span></div>
|
<div class="bpf-price-row total"><span>${t("bpfTotal")}</span><span>€ ${total.toLocaleString("de-DE")}</span></div>
|
||||||
@@ -411,40 +418,16 @@ document.querySelector("#bpfSubmit").addEventListener("click", async () => {
|
|||||||
|
|
||||||
const vehicle = state.vehicles.find(v => v.id === bpfCar.value);
|
const vehicle = state.vehicles.find(v => v.id === bpfCar.value);
|
||||||
const { from, to } = getBpfDates();
|
const { from, to } = getBpfDates();
|
||||||
const vFrom = parseYmdLocal(from);
|
|
||||||
const vTo = parseYmdLocal(to);
|
|
||||||
let weekdayCost = 0, weekendCost = 0, subtotal = 0, vat = 0, total = 0, deposit = 0;
|
|
||||||
let totalDays = 0, weekdays = 0, weekendDays = 0;
|
|
||||||
if (vehicle && vFrom && vTo && vTo > vFrom) {
|
|
||||||
totalDays = Math.ceil((vTo - vFrom) / (1000 * 60 * 60 * 24));
|
|
||||||
weekendDays = bpfDurationMode === "weekend" ? 2 : calcWeekendDays(from, to);
|
|
||||||
weekdays = bpfDurationMode === "weekend" ? 0 : (totalDays - weekendDays);
|
|
||||||
weekdayCost = weekdays * vehicle.daily_price_eur;
|
|
||||||
weekendCost = weekendDays * (vehicle.weekend_price_eur || vehicle.daily_price_eur);
|
|
||||||
subtotal = weekdayCost + weekendCost;
|
|
||||||
vat = Math.round(subtotal * 0.20);
|
|
||||||
total = subtotal + vat;
|
|
||||||
deposit = vehicle.kaution_eur || 5000;
|
|
||||||
}
|
|
||||||
const payload = {
|
const payload = {
|
||||||
p_name: bpfName.value,
|
p_name: bpfName.value,
|
||||||
p_email: bpfEmail.value,
|
p_email: bpfEmail.value,
|
||||||
p_phone: bpfPhone.value || "",
|
p_phone: bpfPhone.value || "",
|
||||||
p_vehicle_id: bpfCar.value || null,
|
p_vehicle_id: bpfCar.value || null,
|
||||||
p_vehicle_label: vehicle ? `${vehicle.brand} ${vehicle.model}` : "",
|
p_vehicle_label: vehicle ? `${vehicle.brand} ${vehicle.model}` : "",
|
||||||
p_date_from: bpfFrom.value || null,
|
p_date_from: from || null,
|
||||||
p_date_to: bpfTo.value || null,
|
p_date_to: to || null,
|
||||||
p_message: bpfMessage.value || "",
|
p_message: bpfMessage.value || "",
|
||||||
p_source: "website",
|
p_source: "website",
|
||||||
p_daily_subtotal: weekdayCost,
|
|
||||||
p_weekend_subtotal: weekendCost,
|
|
||||||
p_subtotal_eur: subtotal,
|
|
||||||
p_vat_eur: vat,
|
|
||||||
p_total_eur: total,
|
|
||||||
p_deposit_eur: deposit,
|
|
||||||
p_total_days: totalDays,
|
|
||||||
p_weekday_count: weekdays,
|
|
||||||
p_weekend_day_count: weekendDays,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create lead via RPC (returns inserted id without anon SELECT privileges)
|
// Create lead via RPC (returns inserted id without anon SELECT privileges)
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
-- 08-backend-pricing-and-security.sql
|
||||||
|
-- 1. Server-side price calculation RPC (read-only, callable by anon for display)
|
||||||
|
-- 2. Refactored create_lead RPC that computes prices internally (no price params from frontend)
|
||||||
|
-- 3. Unique constraint on lead_attachments to enforce max 1 id_document + 1 income_proof per lead
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 1. calculate_price RPC
|
||||||
|
-- =============================================================================
|
||||||
|
create or replace function public.calculate_price(
|
||||||
|
p_vehicle_id uuid,
|
||||||
|
p_date_from date,
|
||||||
|
p_date_to date
|
||||||
|
)
|
||||||
|
returns jsonb
|
||||||
|
language plpgsql
|
||||||
|
stable
|
||||||
|
security definer
|
||||||
|
as $$
|
||||||
|
declare
|
||||||
|
v_vehicle record;
|
||||||
|
v_total_days integer;
|
||||||
|
v_weekend_days integer;
|
||||||
|
v_weekdays integer;
|
||||||
|
v_daily_subtotal integer;
|
||||||
|
v_weekend_subtotal integer;
|
||||||
|
v_subtotal_eur integer;
|
||||||
|
v_vat_eur integer;
|
||||||
|
v_total_eur integer;
|
||||||
|
v_deposit_eur integer;
|
||||||
|
v_cur date;
|
||||||
|
v_dow integer;
|
||||||
|
begin
|
||||||
|
if p_vehicle_id is null or p_date_from is null or p_date_to is null then
|
||||||
|
raise exception 'vehicle_id, date_from and date_to are required';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if p_date_to <= p_date_from then
|
||||||
|
raise exception 'date_to must be after date_from';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select daily_price_eur, weekend_price_eur, kaution_eur, max_daily_km, max_km_weekend
|
||||||
|
into v_vehicle
|
||||||
|
from public.vehicles
|
||||||
|
where id = p_vehicle_id;
|
||||||
|
|
||||||
|
if not found then
|
||||||
|
raise exception 'Vehicle not found';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Count days
|
||||||
|
v_total_days := (p_date_to - p_date_from);
|
||||||
|
v_weekend_days := 0;
|
||||||
|
v_cur := p_date_from;
|
||||||
|
while v_cur < p_date_to loop
|
||||||
|
v_dow := extract(isodow from v_cur); -- 6=Sat, 7=Sun
|
||||||
|
if v_dow in (6, 7) then
|
||||||
|
v_weekend_days := v_weekend_days + 1;
|
||||||
|
end if;
|
||||||
|
v_cur := v_cur + 1;
|
||||||
|
end loop;
|
||||||
|
v_weekdays := v_total_days - v_weekend_days;
|
||||||
|
|
||||||
|
-- Calculate prices
|
||||||
|
v_daily_subtotal := v_weekdays * v_vehicle.daily_price_eur;
|
||||||
|
v_weekend_subtotal := v_weekend_days * (case when v_vehicle.weekend_price_eur > 0 then v_vehicle.weekend_price_eur else v_vehicle.daily_price_eur end);
|
||||||
|
v_subtotal_eur := v_daily_subtotal + v_weekend_subtotal;
|
||||||
|
v_vat_eur := round(v_subtotal_eur * 0.20);
|
||||||
|
v_total_eur := v_subtotal_eur + v_vat_eur;
|
||||||
|
v_deposit_eur := coalesce(nullif(v_vehicle.kaution_eur, 0), 5000);
|
||||||
|
|
||||||
|
return jsonb_build_object(
|
||||||
|
'total_days', v_total_days,
|
||||||
|
'weekday_count', v_weekdays,
|
||||||
|
'weekend_day_count', v_weekend_days,
|
||||||
|
'daily_subtotal', v_daily_subtotal,
|
||||||
|
'weekend_subtotal', v_weekend_subtotal,
|
||||||
|
'subtotal_eur', v_subtotal_eur,
|
||||||
|
'vat_eur', v_vat_eur,
|
||||||
|
'total_eur', v_total_eur,
|
||||||
|
'deposit_eur', v_deposit_eur,
|
||||||
|
'daily_price_eur', v_vehicle.daily_price_eur,
|
||||||
|
'weekend_price_eur', (case when v_vehicle.weekend_price_eur > 0 then v_vehicle.weekend_price_eur else v_vehicle.daily_price_eur end),
|
||||||
|
'max_daily_km', coalesce(v_vehicle.max_daily_km, 150),
|
||||||
|
'max_km_weekend', coalesce(v_vehicle.max_km_weekend, v_vehicle.max_daily_km, 150)
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
grant execute on function public.calculate_price(uuid, date, date) to anon, authenticated, service_role;
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 2. Refactored create_lead – computes prices server-side, no price params
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- Drop old overloaded signatures
|
||||||
|
drop function if exists public.create_lead(
|
||||||
|
text, text, text, uuid, text, date, date, text, text
|
||||||
|
);
|
||||||
|
drop function if exists public.create_lead(
|
||||||
|
text, text, text, uuid, text, date, date, text, text,
|
||||||
|
integer, integer, integer, integer, integer, integer, integer, integer, integer
|
||||||
|
);
|
||||||
|
drop function if exists public.create_lead(
|
||||||
|
text, text, text, uuid, text, date, date, text, text,
|
||||||
|
integer, integer, integer, integer, integer, integer, integer, integer, integer,
|
||||||
|
text, text
|
||||||
|
);
|
||||||
|
|
||||||
|
create or replace function public.create_lead(
|
||||||
|
p_name text,
|
||||||
|
p_email text,
|
||||||
|
p_phone text default '',
|
||||||
|
p_vehicle_id uuid default null,
|
||||||
|
p_vehicle_label text default '',
|
||||||
|
p_date_from date default null,
|
||||||
|
p_date_to date default null,
|
||||||
|
p_message text default '',
|
||||||
|
p_source text default 'website',
|
||||||
|
p_ip_address text default '',
|
||||||
|
p_ip_country text default ''
|
||||||
|
)
|
||||||
|
returns uuid
|
||||||
|
language plpgsql
|
||||||
|
security definer
|
||||||
|
as $$
|
||||||
|
declare
|
||||||
|
v_lead_id uuid;
|
||||||
|
v_vehicle record;
|
||||||
|
v_total_days integer := 0;
|
||||||
|
v_weekend_days integer := 0;
|
||||||
|
v_weekdays integer := 0;
|
||||||
|
v_daily_subtotal integer := 0;
|
||||||
|
v_weekend_subtotal integer := 0;
|
||||||
|
v_subtotal_eur integer := 0;
|
||||||
|
v_vat_eur integer := 0;
|
||||||
|
v_total_eur integer := 0;
|
||||||
|
v_deposit_eur integer := 0;
|
||||||
|
v_cur date;
|
||||||
|
v_dow integer;
|
||||||
|
begin
|
||||||
|
-- Compute prices server-side if vehicle and dates are provided
|
||||||
|
if p_vehicle_id is not null and p_date_from is not null and p_date_to is not null and p_date_to > p_date_from then
|
||||||
|
select daily_price_eur, weekend_price_eur, kaution_eur
|
||||||
|
into v_vehicle
|
||||||
|
from public.vehicles
|
||||||
|
where id = p_vehicle_id;
|
||||||
|
|
||||||
|
if found then
|
||||||
|
v_total_days := (p_date_to - p_date_from);
|
||||||
|
v_cur := p_date_from;
|
||||||
|
while v_cur < p_date_to loop
|
||||||
|
v_dow := extract(isodow from v_cur);
|
||||||
|
if v_dow in (6, 7) then
|
||||||
|
v_weekend_days := v_weekend_days + 1;
|
||||||
|
end if;
|
||||||
|
v_cur := v_cur + 1;
|
||||||
|
end loop;
|
||||||
|
v_weekdays := v_total_days - v_weekend_days;
|
||||||
|
|
||||||
|
v_daily_subtotal := v_weekdays * v_vehicle.daily_price_eur;
|
||||||
|
v_weekend_subtotal := v_weekend_days * (case when v_vehicle.weekend_price_eur > 0 then v_vehicle.weekend_price_eur else v_vehicle.daily_price_eur end);
|
||||||
|
v_subtotal_eur := v_daily_subtotal + v_weekend_subtotal;
|
||||||
|
v_vat_eur := round(v_subtotal_eur * 0.20);
|
||||||
|
v_total_eur := v_subtotal_eur + v_vat_eur;
|
||||||
|
v_deposit_eur := coalesce(nullif(v_vehicle.kaution_eur, 0), 5000);
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
insert into public.leads (
|
||||||
|
name, email, phone, vehicle_id, vehicle_label, date_from, date_to,
|
||||||
|
message, source,
|
||||||
|
daily_subtotal, weekend_subtotal, subtotal_eur, vat_eur, total_eur, deposit_eur,
|
||||||
|
total_days, weekday_count, weekend_day_count, ip_address, ip_country
|
||||||
|
) values (
|
||||||
|
p_name, p_email, p_phone, p_vehicle_id, p_vehicle_label, p_date_from, p_date_to,
|
||||||
|
p_message, p_source,
|
||||||
|
v_daily_subtotal, v_weekend_subtotal, v_subtotal_eur, v_vat_eur, v_total_eur, v_deposit_eur,
|
||||||
|
v_total_days, v_weekdays, v_weekend_days, p_ip_address, p_ip_country
|
||||||
|
)
|
||||||
|
returning id into v_lead_id;
|
||||||
|
return v_lead_id;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
grant execute on function public.create_lead(
|
||||||
|
text, text, text, uuid, text, date, date, text, text, text, text
|
||||||
|
) to anon, authenticated, service_role;
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 3. Enforce max 1 id_document + 1 income_proof per lead
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- Unique partial index: only one 'id_document' per lead
|
||||||
|
drop index if exists lead_attachments_unique_id_document;
|
||||||
|
create unique index lead_attachments_unique_id_document
|
||||||
|
on public.lead_attachments (lead_id)
|
||||||
|
where kind = 'id_document';
|
||||||
|
|
||||||
|
-- Unique partial index: only one 'income_proof' per lead
|
||||||
|
drop index if exists lead_attachments_unique_income_proof;
|
||||||
|
create unique index lead_attachments_unique_income_proof
|
||||||
|
on public.lead_attachments (lead_id)
|
||||||
|
where kind = 'income_proof';
|
||||||
|
|
||||||
|
notify pgrst, 'reload schema';
|
||||||
@@ -65,9 +65,7 @@ grant anon, authenticated, service_role to supabase_storage_admin;
|
|||||||
|
|
||||||
grant select on storage.buckets to anon, authenticated;
|
grant select on storage.buckets to anon, authenticated;
|
||||||
grant all on storage.buckets to service_role;
|
grant all on storage.buckets to service_role;
|
||||||
grant select on storage.objects to anon;
|
|
||||||
grant insert on storage.objects to anon;
|
grant insert on storage.objects to anon;
|
||||||
grant update on storage.objects to anon;
|
|
||||||
grant select, insert, update, delete on storage.objects to authenticated;
|
grant select, insert, update, delete on storage.objects to authenticated;
|
||||||
grant all on storage.objects to service_role;
|
grant all on storage.objects to service_role;
|
||||||
|
|
||||||
@@ -110,27 +108,19 @@ drop policy if exists "custdocs_public_upload" on storage.objects;
|
|||||||
drop policy if exists "custdocs_public_upsert_update" on storage.objects;
|
drop policy if exists "custdocs_public_upsert_update" on storage.objects;
|
||||||
drop policy if exists "custdocs_admin_read" on storage.objects;
|
drop policy if exists "custdocs_admin_read" on storage.objects;
|
||||||
drop policy if exists "custdocs_admin_delete" on storage.objects;
|
drop policy if exists "custdocs_admin_delete" on storage.objects;
|
||||||
|
drop policy if exists "custdocs_admin_insert" on storage.objects;
|
||||||
|
|
||||||
-- Anon can upload (insert) during booking flow
|
-- Anon can only INSERT (upload) during booking flow — no SELECT/UPDATE/DELETE
|
||||||
create policy "custdocs_anon_upload"
|
create policy "custdocs_anon_upload"
|
||||||
on storage.objects for insert to anon
|
on storage.objects for insert to anon
|
||||||
with check (bucket_id = 'customer-documents');
|
with check (bucket_id = 'customer-documents');
|
||||||
|
|
||||||
-- Anon needs SELECT + UPDATE for x-upsert to work (Supabase storage requirement)
|
-- Authenticated admins can read (view documents)
|
||||||
create policy "custdocs_anon_select"
|
|
||||||
on storage.objects for select to anon
|
|
||||||
using (bucket_id = 'customer-documents');
|
|
||||||
|
|
||||||
create policy "custdocs_anon_update"
|
|
||||||
on storage.objects for update to anon
|
|
||||||
using (bucket_id = 'customer-documents')
|
|
||||||
with check (bucket_id = 'customer-documents');
|
|
||||||
|
|
||||||
-- Authenticated admins can read/delete
|
|
||||||
create policy "custdocs_admin_read"
|
create policy "custdocs_admin_read"
|
||||||
on storage.objects for select to authenticated
|
on storage.objects for select to authenticated
|
||||||
using (bucket_id = 'customer-documents');
|
using (bucket_id = 'customer-documents');
|
||||||
|
|
||||||
create policy "custdocs_admin_delete"
|
-- Authenticated admins can upload new documents
|
||||||
on storage.objects for delete to authenticated
|
create policy "custdocs_admin_insert"
|
||||||
using (bucket_id = 'customer-documents');
|
on storage.objects for insert to authenticated
|
||||||
|
with check (bucket_id = 'customer-documents');
|
||||||
|
|||||||
Reference in New Issue
Block a user