-- 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);