initial commit: rocket.new export of broadcastbeat
This commit is contained in:
185
supabase/migrations/20260321190000_forum_votes_reputation.sql
Normal file
185
supabase/migrations/20260321190000_forum_votes_reputation.sql
Normal file
@@ -0,0 +1,185 @@
|
||||
-- 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();
|
||||
Reference in New Issue
Block a user