ALTER TABLE public.board_vote_responses ADD COLUMN IF NOT EXISTS board_member_id uuid REFERENCES public.board_members(id) ON DELETE CASCADE; ALTER TABLE public.board_vote_responses ALTER COLUMN user_id DROP NOT NULL; ALTER TABLE public.board_vote_responses DROP CONSTRAINT IF EXISTS board_vote_responses_board_vote_id_user_id_key; CREATE UNIQUE INDEX IF NOT EXISTS board_vote_responses_unique_user ON public.board_vote_responses(board_vote_id, user_id) WHERE user_id IS NOT NULL; CREATE UNIQUE INDEX IF NOT EXISTS board_vote_responses_unique_member ON public.board_vote_responses(board_vote_id, board_member_id) WHERE board_member_id IS NOT NULL; ALTER TABLE public.board_vote_responses ADD CONSTRAINT board_vote_responses_identity_required CHECK (user_id IS NOT NULL OR board_member_id IS NOT NULL); CREATE OR REPLACE FUNCTION public.record_board_vote_by_token(p_token uuid, p_option text) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $function$ DECLARE v_token RECORD; v_vote RECORD; v_member RECORD; BEGIN SELECT * INTO v_token FROM public.board_vote_email_tokens WHERE token = p_token; IF v_token IS NULL THEN RETURN jsonb_build_object('ok', false, 'error', 'Invalid token'); END IF; IF v_token.voted_at IS NOT NULL THEN RETURN jsonb_build_object('ok', false, 'error', 'Vote already recorded', 'already_voted', true); END IF; SELECT * INTO v_vote FROM public.board_votes WHERE id = v_token.board_vote_id; IF v_vote.status <> 'open' THEN RETURN jsonb_build_object('ok', false, 'error', 'Voting is closed for this item'); END IF; IF NOT (p_option = ANY(v_vote.vote_options)) THEN RETURN jsonb_build_object('ok', false, 'error', 'Invalid vote option'); END IF; SELECT * INTO v_member FROM public.board_members WHERE id = v_token.board_member_id; -- Record token usage UPDATE public.board_vote_email_tokens SET voted_at = now(), vote_option = p_option WHERE id = v_token.id; -- Mirror into board_vote_responses so it shows in the tally IF v_member.user_id IS NOT NULL THEN INSERT INTO public.board_vote_responses (board_vote_id, user_id, board_member_id, vote_option) VALUES (v_token.board_vote_id, v_member.user_id, v_token.board_member_id, p_option) ON CONFLICT (board_vote_id, user_id) WHERE user_id IS NOT NULL DO UPDATE SET vote_option = EXCLUDED.vote_option, updated_at = now(); ELSE INSERT INTO public.board_vote_responses (board_vote_id, board_member_id, vote_option) VALUES (v_token.board_vote_id, v_token.board_member_id, p_option) ON CONFLICT (board_vote_id, board_member_id) WHERE board_member_id IS NOT NULL DO UPDATE SET vote_option = EXCLUDED.vote_option, updated_at = now(); END IF; RETURN jsonb_build_object('ok', true); END; $function$;