mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
97 lines
3.7 KiB
PL/PgSQL
97 lines
3.7 KiB
PL/PgSQL
|
|
-- Per-bill, per-board-member secure approval tokens for email approve/deny
|
|
CREATE TABLE IF NOT EXISTS public.bill_approval_email_tokens (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
bill_id uuid NOT NULL REFERENCES public.bills(id) ON DELETE CASCADE,
|
|
board_member_id uuid NOT NULL REFERENCES public.board_members(id) ON DELETE CASCADE,
|
|
token uuid NOT NULL DEFAULT gen_random_uuid() UNIQUE,
|
|
email text NOT NULL,
|
|
member_name text,
|
|
sent_at timestamptz,
|
|
acted_at timestamptz,
|
|
action text CHECK (action IN ('approved','denied')),
|
|
notes text,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (bill_id, board_member_id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_baet_token ON public.bill_approval_email_tokens(token);
|
|
CREATE INDEX IF NOT EXISTS idx_baet_bill ON public.bill_approval_email_tokens(bill_id);
|
|
|
|
ALTER TABLE public.bill_approval_email_tokens ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "Staff manage bill approval tokens" ON public.bill_approval_email_tokens
|
|
TO authenticated
|
|
USING (has_role(auth.uid(),'admin') OR has_role(auth.uid(),'manager'))
|
|
WITH CHECK (has_role(auth.uid(),'admin') OR has_role(auth.uid(),'manager'));
|
|
|
|
-- Lookup RPC for the public page (anonymous-friendly, security definer)
|
|
CREATE OR REPLACE FUNCTION public.lookup_bill_approval_by_token(p_token uuid)
|
|
RETURNS TABLE (
|
|
bill_id uuid,
|
|
member_name text,
|
|
member_email text,
|
|
acted_at timestamptz,
|
|
action text,
|
|
vendor_name text,
|
|
invoice_number text,
|
|
bill_date date,
|
|
due_date date,
|
|
amount numeric,
|
|
description text,
|
|
attachment_url text,
|
|
bill_status text,
|
|
association_name text
|
|
)
|
|
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public
|
|
AS $$
|
|
SELECT t.bill_id, t.member_name, t.email AS member_email, t.acted_at, t.action,
|
|
v.name AS vendor_name, b.invoice_number, b.bill_date, b.due_date, b.amount,
|
|
b.description, b.attachment_url, b.status AS bill_status, a.name AS association_name
|
|
FROM bill_approval_email_tokens t
|
|
JOIN bills b ON b.id = t.bill_id
|
|
LEFT JOIN vendors v ON v.id = b.vendor_id
|
|
LEFT JOIN associations a ON a.id = b.association_id
|
|
WHERE t.token = p_token;
|
|
$$;
|
|
GRANT EXECUTE ON FUNCTION public.lookup_bill_approval_by_token(uuid) TO anon, authenticated;
|
|
|
|
-- Record action via token
|
|
CREATE OR REPLACE FUNCTION public.record_bill_approval_by_token(p_token uuid, p_action text, p_notes text DEFAULT NULL)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
|
|
AS $$
|
|
DECLARE
|
|
tok record;
|
|
bill record;
|
|
BEGIN
|
|
IF p_action NOT IN ('approved','denied') THEN
|
|
RETURN jsonb_build_object('ok', false, 'error', 'Invalid action');
|
|
END IF;
|
|
|
|
SELECT * INTO tok FROM bill_approval_email_tokens WHERE token = p_token;
|
|
IF NOT FOUND THEN
|
|
RETURN jsonb_build_object('ok', false, 'error', 'Invalid token');
|
|
END IF;
|
|
IF tok.acted_at IS NOT NULL THEN
|
|
RETURN jsonb_build_object('ok', false, 'already_voted', true, 'error', 'You have already responded', 'action', tok.action);
|
|
END IF;
|
|
|
|
SELECT * INTO bill FROM bills WHERE id = tok.bill_id;
|
|
IF NOT FOUND THEN
|
|
RETURN jsonb_build_object('ok', false, 'error', 'Bill not found');
|
|
END IF;
|
|
|
|
INSERT INTO bill_approvals (bill_id, association_id, vendor_name, amount, status, notes, approved_date)
|
|
VALUES (tok.bill_id, bill.association_id, COALESCE(tok.member_name,'Board Member'), bill.amount,
|
|
p_action, p_notes, CURRENT_DATE);
|
|
|
|
UPDATE bills SET status = p_action, updated_at = now() WHERE id = tok.bill_id;
|
|
|
|
UPDATE bill_approval_email_tokens
|
|
SET acted_at = now(), action = p_action, notes = p_notes
|
|
WHERE token = p_token;
|
|
|
|
RETURN jsonb_build_object('ok', true, 'action', p_action);
|
|
END;
|
|
$$;
|
|
GRANT EXECUTE ON FUNCTION public.record_bill_approval_by_token(uuid, text, text) TO anon, authenticated;
|