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>
155 lines
7.4 KiB
SQL
155 lines
7.4 KiB
SQL
|
|
-- Elections table
|
|
CREATE TABLE public.elections (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
election_type TEXT NOT NULL DEFAULT 'board_election',
|
|
status TEXT NOT NULL DEFAULT 'draft',
|
|
voting_start TIMESTAMPTZ,
|
|
voting_end TIMESTAMPTZ,
|
|
results_locked BOOLEAN NOT NULL DEFAULT true,
|
|
quorum_required INTEGER NOT NULL DEFAULT 0,
|
|
proxies_count INTEGER NOT NULL DEFAULT 0,
|
|
in_person_count INTEGER NOT NULL DEFAULT 0,
|
|
created_by UUID REFERENCES auth.users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Election positions (e.g. President, Treasurer)
|
|
CREATE TABLE public.election_positions (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
election_id UUID NOT NULL REFERENCES public.elections(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
seats_available INTEGER NOT NULL DEFAULT 1,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Election candidates
|
|
CREATE TABLE public.election_candidates (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
position_id UUID NOT NULL REFERENCES public.election_positions(id) ON DELETE CASCADE,
|
|
candidate_name TEXT NOT NULL,
|
|
bio TEXT,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Eligible voters (one vote per unit)
|
|
CREATE TABLE public.election_eligible_voters (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
election_id UUID NOT NULL REFERENCES public.elections(id) ON DELETE CASCADE,
|
|
owner_id UUID NOT NULL REFERENCES public.owners(id) ON DELETE CASCADE,
|
|
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
|
has_consent BOOLEAN NOT NULL DEFAULT false,
|
|
consent_date TIMESTAMPTZ,
|
|
vote_token UUID UNIQUE DEFAULT gen_random_uuid(),
|
|
has_voted BOOLEAN NOT NULL DEFAULT false,
|
|
voted_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE(election_id, unit_id)
|
|
);
|
|
|
|
-- Anonymous ballots (no link to voter identity - linked only to token)
|
|
CREATE TABLE public.election_ballots (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
election_id UUID NOT NULL REFERENCES public.elections(id) ON DELETE CASCADE,
|
|
position_id UUID NOT NULL REFERENCES public.election_positions(id) ON DELETE CASCADE,
|
|
candidate_id UUID REFERENCES public.election_candidates(id) ON DELETE SET NULL,
|
|
vote_token UUID NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Audit log (tracks who voted but NOT how)
|
|
CREATE TABLE public.election_audit_log (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
election_id UUID NOT NULL REFERENCES public.elections(id) ON DELETE CASCADE,
|
|
voter_description TEXT NOT NULL,
|
|
action TEXT NOT NULL DEFAULT 'voted',
|
|
ip_address TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Enable RLS
|
|
ALTER TABLE public.elections ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.election_positions ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.election_candidates ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.election_eligible_voters ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.election_ballots ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.election_audit_log ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Elections: staff full access, owners can read their own association's elections
|
|
CREATE POLICY "Staff can manage elections" ON public.elections
|
|
FOR ALL TO authenticated
|
|
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager') OR public.has_role(auth.uid(), 'employee'));
|
|
|
|
CREATE POLICY "Owners can view their association elections" ON public.elections
|
|
FOR SELECT TO authenticated
|
|
USING (association_id IN (SELECT public.get_user_association_ids()));
|
|
|
|
-- Positions: staff full access, authenticated read
|
|
CREATE POLICY "Staff can manage positions" ON public.election_positions
|
|
FOR ALL TO authenticated
|
|
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager') OR public.has_role(auth.uid(), 'employee'));
|
|
|
|
CREATE POLICY "Authenticated can view positions" ON public.election_positions
|
|
FOR SELECT TO authenticated
|
|
USING (election_id IN (SELECT id FROM public.elections WHERE association_id IN (SELECT public.get_user_association_ids())));
|
|
|
|
-- Candidates: staff full access, authenticated read
|
|
CREATE POLICY "Staff can manage candidates" ON public.election_candidates
|
|
FOR ALL TO authenticated
|
|
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager') OR public.has_role(auth.uid(), 'employee'));
|
|
|
|
CREATE POLICY "Authenticated can view candidates" ON public.election_candidates
|
|
FOR SELECT TO authenticated
|
|
USING (position_id IN (SELECT id FROM public.election_positions WHERE election_id IN (SELECT id FROM public.elections WHERE association_id IN (SELECT public.get_user_association_ids()))));
|
|
|
|
-- Eligible voters: staff full access, voters can see their own record
|
|
CREATE POLICY "Staff can manage eligible voters" ON public.election_eligible_voters
|
|
FOR ALL TO authenticated
|
|
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager') OR public.has_role(auth.uid(), 'employee'));
|
|
|
|
CREATE POLICY "Owners can view their own voter record" ON public.election_eligible_voters
|
|
FOR SELECT TO authenticated
|
|
USING (owner_id IN (SELECT id FROM public.owners WHERE user_id = auth.uid()));
|
|
|
|
-- Ballots: staff can read (for tallying), voters can insert/update their own
|
|
CREATE POLICY "Staff can view ballots" ON public.election_ballots
|
|
FOR SELECT TO authenticated
|
|
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'));
|
|
|
|
CREATE POLICY "Voters can insert ballots" ON public.election_ballots
|
|
FOR INSERT TO authenticated
|
|
WITH CHECK (vote_token IN (SELECT vote_token FROM public.election_eligible_voters WHERE owner_id IN (SELECT id FROM public.owners WHERE user_id = auth.uid())));
|
|
|
|
CREATE POLICY "Voters can update their ballots" ON public.election_ballots
|
|
FOR UPDATE TO authenticated
|
|
USING (vote_token IN (SELECT vote_token FROM public.election_eligible_voters WHERE owner_id IN (SELECT id FROM public.owners WHERE user_id = auth.uid())));
|
|
|
|
CREATE POLICY "Voters can delete their ballots" ON public.election_ballots
|
|
FOR DELETE TO authenticated
|
|
USING (vote_token IN (SELECT vote_token FROM public.election_eligible_voters WHERE owner_id IN (SELECT id FROM public.owners WHERE user_id = auth.uid())));
|
|
|
|
-- Audit log: staff can read, system inserts
|
|
CREATE POLICY "Staff can view audit log" ON public.election_audit_log
|
|
FOR SELECT TO authenticated
|
|
USING (public.has_role(auth.uid(), 'admin') OR public.has_role(auth.uid(), 'manager'));
|
|
|
|
CREATE POLICY "Authenticated can insert audit log" ON public.election_audit_log
|
|
FOR INSERT TO authenticated
|
|
WITH CHECK (true);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_elections_association ON public.elections(association_id);
|
|
CREATE INDEX idx_elections_status ON public.elections(status);
|
|
CREATE INDEX idx_election_positions_election ON public.election_positions(election_id);
|
|
CREATE INDEX idx_election_candidates_position ON public.election_candidates(position_id);
|
|
CREATE INDEX idx_election_eligible_voters_election ON public.election_eligible_voters(election_id);
|
|
CREATE INDEX idx_election_eligible_voters_token ON public.election_eligible_voters(vote_token);
|
|
CREATE INDEX idx_election_ballots_election ON public.election_ballots(election_id);
|
|
CREATE INDEX idx_election_ballots_token ON public.election_ballots(vote_token);
|