Files
avbeat-com/supabase/migrations/20260321190000_forum_votes_reputation.sql
2026-05-07 16:39:17 +00:00

186 lines
7.0 KiB
PL/PgSQL

-- Forum Votes & Reputation Migration for BroadcastBeat
-- Adds upvote/downvote on threads and replies, and reputation points on user_profiles
-- ============================================================
-- 1. ADD REPUTATION COLUMN TO USER_PROFILES
-- ============================================================
ALTER TABLE public.user_profiles
ADD COLUMN IF NOT EXISTS reputation INTEGER DEFAULT 0;
-- ============================================================
-- 2. FORUM VOTES TABLE
-- ============================================================
CREATE TABLE IF NOT EXISTS public.forum_votes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.user_profiles(id) ON DELETE CASCADE,
target_type TEXT NOT NULL CHECK (target_type IN ('thread', 'reply')),
target_id UUID NOT NULL,
vote_value SMALLINT NOT NULL CHECK (vote_value IN (1, -1)),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Unique: one vote per user per target
CREATE UNIQUE INDEX IF NOT EXISTS idx_forum_votes_unique
ON public.forum_votes (user_id, target_type, target_id);
CREATE INDEX IF NOT EXISTS idx_forum_votes_target
ON public.forum_votes (target_type, target_id);
CREATE INDEX IF NOT EXISTS idx_forum_votes_user_id
ON public.forum_votes (user_id);
-- ============================================================
-- 3. ADD VOTE SCORE COLUMNS TO THREADS AND REPLIES
-- ============================================================
ALTER TABLE public.forum_threads
ADD COLUMN IF NOT EXISTS upvotes INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS downvotes INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS vote_score INTEGER DEFAULT 0;
ALTER TABLE public.forum_replies
ADD COLUMN IF NOT EXISTS upvotes INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS downvotes INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS vote_score INTEGER DEFAULT 0;
-- ============================================================
-- 4. FUNCTIONS
-- ============================================================
-- Recalculate vote counts on thread/reply after vote change
CREATE OR REPLACE FUNCTION public.update_vote_counts()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_target_type TEXT;
v_target_id UUID;
v_up INTEGER;
v_down INTEGER;
v_author_id UUID;
v_old_vote SMALLINT := 0;
v_new_vote SMALLINT := 0;
v_rep_delta INTEGER := 0;
BEGIN
-- Determine target info
IF TG_OP = 'DELETE' THEN
v_target_type := OLD.target_type;
v_target_id := OLD.target_id;
v_old_vote := OLD.vote_value;
ELSIF TG_OP = 'INSERT' THEN
v_target_type := NEW.target_type;
v_target_id := NEW.target_id;
v_new_vote := NEW.vote_value;
ELSIF TG_OP = 'UPDATE' THEN
v_target_type := NEW.target_type;
v_target_id := NEW.target_id;
v_old_vote := OLD.vote_value;
v_new_vote := NEW.vote_value;
END IF;
-- Recalculate vote totals
SELECT
COALESCE(SUM(CASE WHEN vote_value = 1 THEN 1 ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN vote_value = -1 THEN 1 ELSE 0 END), 0)
INTO v_up, v_down
FROM public.forum_votes
WHERE target_type = v_target_type AND target_id = v_target_id;
-- Update the appropriate table
IF v_target_type = 'thread' THEN
UPDATE public.forum_threads
SET upvotes = v_up,
downvotes = v_down,
vote_score = v_up - v_down,
updated_at = CURRENT_TIMESTAMP
WHERE id = v_target_id;
SELECT author_id INTO v_author_id
FROM public.forum_threads WHERE id = v_target_id LIMIT 1;
ELSIF v_target_type = 'reply' THEN
UPDATE public.forum_replies
SET upvotes = v_up,
downvotes = v_down,
vote_score = v_up - v_down,
updated_at = CURRENT_TIMESTAMP
WHERE id = v_target_id;
SELECT author_id INTO v_author_id
FROM public.forum_replies WHERE id = v_target_id LIMIT 1;
END IF;
-- Update author reputation (only for authenticated authors)
IF v_author_id IS NOT NULL THEN
-- Calculate reputation delta: upvote = +10, downvote = -2
v_rep_delta := (v_new_vote - v_old_vote) * CASE
WHEN (v_new_vote > 0 OR v_old_vote > 0) THEN 10
ELSE 2
END;
-- More precise: +10 per upvote gained, -10 per upvote lost, -2 per downvote gained, +2 per downvote lost
v_rep_delta := 0;
IF v_new_vote = 1 AND v_old_vote = 0 THEN v_rep_delta := 10; END IF;
IF v_new_vote = 0 AND v_old_vote = 1 THEN v_rep_delta := -10; END IF;
IF v_new_vote = -1 AND v_old_vote = 0 THEN v_rep_delta := -2; END IF;
IF v_new_vote = 0 AND v_old_vote = -1 THEN v_rep_delta := 2; END IF;
IF v_new_vote = 1 AND v_old_vote = -1 THEN v_rep_delta := 12; END IF;
IF v_new_vote = -1 AND v_old_vote = 1 THEN v_rep_delta := -12; END IF;
IF v_rep_delta != 0 THEN
UPDATE public.user_profiles
SET reputation = GREATEST(0, COALESCE(reputation, 0) + v_rep_delta)
WHERE id = v_author_id;
END IF;
END IF;
RETURN COALESCE(NEW, OLD);
END;
$$;
-- ============================================================
-- 5. ENABLE RLS
-- ============================================================
ALTER TABLE public.forum_votes ENABLE ROW LEVEL SECURITY;
-- ============================================================
-- 6. RLS POLICIES
-- ============================================================
-- Public can read all votes (for counts)
DROP POLICY IF EXISTS "public_read_forum_votes" ON public.forum_votes;
CREATE POLICY "public_read_forum_votes"
ON public.forum_votes FOR SELECT TO public USING (true);
-- Authenticated users can insert their own votes
DROP POLICY IF EXISTS "authenticated_insert_forum_votes" ON public.forum_votes;
CREATE POLICY "authenticated_insert_forum_votes"
ON public.forum_votes FOR INSERT TO authenticated
WITH CHECK (user_id = auth.uid());
-- Users can update their own votes
DROP POLICY IF EXISTS "users_update_own_forum_votes" ON public.forum_votes;
CREATE POLICY "users_update_own_forum_votes"
ON public.forum_votes FOR UPDATE TO authenticated
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());
-- Users can delete their own votes
DROP POLICY IF EXISTS "users_delete_own_forum_votes" ON public.forum_votes;
CREATE POLICY "users_delete_own_forum_votes"
ON public.forum_votes FOR DELETE TO authenticated
USING (user_id = auth.uid());
-- Service role full access
DROP POLICY IF EXISTS "service_role_manage_forum_votes" ON public.forum_votes;
CREATE POLICY "service_role_manage_forum_votes"
ON public.forum_votes FOR ALL TO service_role USING (true) WITH CHECK (true);
-- ============================================================
-- 7. TRIGGERS
-- ============================================================
DROP TRIGGER IF EXISTS on_forum_vote_change ON public.forum_votes;
CREATE TRIGGER on_forum_vote_change
AFTER INSERT OR UPDATE OR DELETE ON public.forum_votes
FOR EACH ROW EXECUTE FUNCTION public.update_vote_counts();