refactor: remove obsolete member management API module
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
-- =====================================================
|
||||
-- Member Communications Tracking
|
||||
--
|
||||
-- Records all communications with/about members:
|
||||
-- emails sent, phone calls, notes, letters, meetings.
|
||||
-- Communications are append-only for authenticated users.
|
||||
-- Only service_role (admin) can delete.
|
||||
-- Integrates with audit log via triggers.
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.member_communications (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
member_id uuid NOT NULL REFERENCES public.members(id) ON DELETE CASCADE,
|
||||
account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
|
||||
type text NOT NULL CHECK (type IN ('email', 'phone', 'letter', 'meeting', 'note', 'sms')),
|
||||
direction text NOT NULL DEFAULT 'outbound' CHECK (direction IN ('inbound', 'outbound', 'internal')),
|
||||
subject text CHECK (subject IS NULL OR length(subject) <= 500),
|
||||
body text CHECK (body IS NULL OR length(body) <= 50000),
|
||||
-- Email-specific fields
|
||||
email_to text,
|
||||
email_cc text,
|
||||
email_message_id text,
|
||||
-- Attachment references (Supabase Storage paths)
|
||||
attachment_paths text[] CHECK (attachment_paths IS NULL OR array_length(attachment_paths, 1) <= 10),
|
||||
-- Audit
|
||||
created_by uuid NOT NULL REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.member_communications IS
|
||||
'Communication log per member — emails, calls, notes, letters, meetings. Append-only for regular users.';
|
||||
|
||||
CREATE INDEX ix_member_comms_member
|
||||
ON public.member_communications(member_id, created_at DESC);
|
||||
CREATE INDEX ix_member_comms_account
|
||||
ON public.member_communications(account_id, created_at DESC);
|
||||
CREATE INDEX ix_member_comms_type
|
||||
ON public.member_communications(account_id, type);
|
||||
|
||||
ALTER TABLE public.member_communications ENABLE ROW LEVEL SECURITY;
|
||||
REVOKE ALL ON public.member_communications FROM authenticated, service_role;
|
||||
-- Append-only: authenticated users can SELECT + INSERT, not UPDATE/DELETE
|
||||
GRANT SELECT, INSERT ON public.member_communications TO authenticated;
|
||||
GRANT ALL ON public.member_communications TO service_role;
|
||||
|
||||
-- Read: must have a role on the account
|
||||
CREATE POLICY member_comms_select
|
||||
ON public.member_communications FOR SELECT TO authenticated
|
||||
USING (public.has_role_on_account(account_id));
|
||||
|
||||
-- Insert: must have members.write permission
|
||||
CREATE POLICY member_comms_insert
|
||||
ON public.member_communications FOR INSERT TO authenticated
|
||||
WITH CHECK (public.has_permission(auth.uid(), account_id, 'members.write'::public.app_permissions));
|
||||
|
||||
-- No UPDATE/DELETE policies for authenticated — communications are immutable
|
||||
-- service_role can still delete via admin client when necessary
|
||||
|
||||
-- Auto-log to audit trail on communication INSERT
|
||||
CREATE OR REPLACE FUNCTION public.trg_member_comm_audit_insert()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.member_audit_log (
|
||||
member_id, account_id, user_id, action, metadata
|
||||
) VALUES (
|
||||
NEW.member_id,
|
||||
NEW.account_id,
|
||||
NEW.created_by,
|
||||
'communication_logged',
|
||||
jsonb_build_object(
|
||||
'communication_id', NEW.id,
|
||||
'type', NEW.type,
|
||||
'direction', NEW.direction,
|
||||
'subject', NEW.subject
|
||||
)
|
||||
);
|
||||
RETURN NEW;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
-- Audit failure should not block the insert
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_member_comms_audit_insert
|
||||
AFTER INSERT ON public.member_communications
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.trg_member_comm_audit_insert();
|
||||
|
||||
-- Safe delete function for admin use — logs before deleting
|
||||
CREATE OR REPLACE FUNCTION public.delete_member_communication(
|
||||
p_communication_id uuid,
|
||||
p_account_id uuid
|
||||
)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
DECLARE
|
||||
v_comm record;
|
||||
BEGIN
|
||||
-- Verify caller has access
|
||||
IF NOT public.has_permission(auth.uid(), p_account_id, 'members.write'::public.app_permissions) THEN
|
||||
RAISE EXCEPTION 'Access denied' USING ERRCODE = '42501';
|
||||
END IF;
|
||||
|
||||
-- Fetch the communication for audit
|
||||
SELECT * INTO v_comm
|
||||
FROM public.member_communications
|
||||
WHERE id = p_communication_id AND account_id = p_account_id;
|
||||
|
||||
IF v_comm IS NULL THEN
|
||||
RAISE EXCEPTION 'Communication not found' USING ERRCODE = 'P0002';
|
||||
END IF;
|
||||
|
||||
-- Log deletion to audit trail
|
||||
INSERT INTO public.member_audit_log (
|
||||
member_id, account_id, user_id, action, metadata
|
||||
) VALUES (
|
||||
v_comm.member_id,
|
||||
v_comm.account_id,
|
||||
auth.uid(),
|
||||
'communication_logged',
|
||||
jsonb_build_object(
|
||||
'deleted_communication_id', v_comm.id,
|
||||
'type', v_comm.type,
|
||||
'direction', v_comm.direction,
|
||||
'subject', v_comm.subject,
|
||||
'action_detail', 'deleted'
|
||||
)
|
||||
);
|
||||
|
||||
-- Delete via service_role context (SECURITY DEFINER bypasses RLS)
|
||||
DELETE FROM public.member_communications
|
||||
WHERE id = p_communication_id AND account_id = p_account_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
GRANT EXECUTE ON FUNCTION public.delete_member_communication(uuid, uuid)
|
||||
TO authenticated, service_role;
|
||||
Reference in New Issue
Block a user