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

130 lines
4.0 KiB
PL/PgSQL

-- User Suspensions Migration
-- Adds user_suspensions table for admin suspend/ban controls
-- 1. Create suspension type enum
DROP TYPE IF EXISTS public.suspension_type CASCADE;
CREATE TYPE public.suspension_type AS ENUM ('suspended', 'banned');
-- 2. Create user_suspensions table
CREATE TABLE IF NOT EXISTS public.user_suspensions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES public.user_profiles(id) ON DELETE CASCADE,
suspended_by UUID NOT NULL REFERENCES public.user_profiles(id) ON DELETE CASCADE,
suspension_type public.suspension_type NOT NULL DEFAULT 'suspended',
reason TEXT NOT NULL,
duration_days INTEGER NULL, -- NULL = permanent (ban)
expires_at TIMESTAMPTZ NULL, -- NULL = permanent
is_active BOOLEAN NOT NULL DEFAULT true,
lifted_at TIMESTAMPTZ NULL,
lifted_by UUID NULL REFERENCES public.user_profiles(id) ON DELETE SET NULL,
lift_reason TEXT NULL,
email_sent BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 3. Indexes
CREATE INDEX IF NOT EXISTS idx_user_suspensions_user_id ON public.user_suspensions(user_id);
CREATE INDEX IF NOT EXISTS idx_user_suspensions_active ON public.user_suspensions(user_id, is_active);
CREATE INDEX IF NOT EXISTS idx_user_suspensions_expires_at ON public.user_suspensions(expires_at) WHERE is_active = true;
-- 4. Add suspension_status column to user_profiles if not exists
ALTER TABLE public.user_profiles
ADD COLUMN IF NOT EXISTS suspension_status TEXT DEFAULT NULL,
ADD COLUMN IF NOT EXISTS suspension_reason TEXT DEFAULT NULL,
ADD COLUMN IF NOT EXISTS suspended_until TIMESTAMPTZ DEFAULT NULL;
-- 5. Helper function: check if user is currently suspended
CREATE OR REPLACE FUNCTION public.is_user_suspended(p_user_id UUID)
RETURNS BOOLEAN
LANGUAGE sql
STABLE
SECURITY DEFINER
AS $$
SELECT EXISTS (
SELECT 1 FROM public.user_suspensions
WHERE user_id = p_user_id
AND is_active = true
AND (expires_at IS NULL OR expires_at > now())
)
$$;
-- 6. Helper function: get active suspension for user
CREATE OR REPLACE FUNCTION public.get_active_suspension(p_user_id UUID)
RETURNS TABLE(
suspension_id UUID,
suspension_type TEXT,
reason TEXT,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ
)
LANGUAGE sql
STABLE
SECURITY DEFINER
AS $$
SELECT id, suspension_type::TEXT, reason, expires_at, created_at
FROM public.user_suspensions
WHERE user_id = p_user_id
AND is_active = true
AND (expires_at IS NULL OR expires_at > now())
ORDER BY created_at DESC
LIMIT 1
$$;
-- 7. Function to auto-expire suspensions
CREATE OR REPLACE FUNCTION public.expire_suspensions()
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
UPDATE public.user_suspensions
SET is_active = false, updated_at = now()
WHERE is_active = true
AND expires_at IS NOT NULL
AND expires_at <= now();
-- Sync user_profiles suspension_status
UPDATE public.user_profiles
SET suspension_status = NULL,
suspension_reason = NULL,
suspended_until = NULL
WHERE id IN (
SELECT DISTINCT user_id FROM public.user_suspensions
WHERE is_active = false
AND expires_at IS NOT NULL
AND expires_at <= now()
)
AND NOT public.is_user_suspended(id);
END;
$$;
-- 8. Enable RLS
ALTER TABLE public.user_suspensions ENABLE ROW LEVEL SECURITY;
-- 9. RLS Policies
DROP POLICY IF EXISTS "admins_manage_suspensions" ON public.user_suspensions;
CREATE POLICY "admins_manage_suspensions"
ON public.user_suspensions
FOR ALL
TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.user_profiles
WHERE id = auth.uid() AND role IN ('admin', 'moderator')
)
)
WITH CHECK (
EXISTS (
SELECT 1 FROM public.user_profiles
WHERE id = auth.uid() AND role IN ('admin', 'moderator')
)
);
DROP POLICY IF EXISTS "users_view_own_suspension" ON public.user_suspensions;
CREATE POLICY "users_view_own_suspension"
ON public.user_suspensions
FOR SELECT
TO authenticated
USING (user_id = auth.uid());