-- ===================================================== -- 一键部署脚本 - Supabase消息系统完整部署 -- 按顺序执行以下所有SQL代码块 -- ===================================================== -- 第一步:清理和准备 DO $$ BEGIN RAISE NOTICE '🚀 开始部署Supabase消息系统...'; RAISE NOTICE '📋 第一步:清理现有配置'; END $$; -- 删除可能存在的测试数据 DELETE FROM public.ak_message_recipients WHERE message_id IN ( SELECT id FROM public.ak_messages WHERE metadata ? 'test_data' ); DELETE FROM public.ak_messages WHERE metadata ? 'test_data'; DELETE FROM public.ak_message_group_members WHERE group_id IN ( SELECT id FROM public.ak_message_groups WHERE metadata ? 'test_data' ); DELETE FROM public.ak_message_groups WHERE metadata ? 'test_data'; -- 第二步:验证基础表结构 DO $$ DECLARE table_count INTEGER; BEGIN RAISE NOTICE '📋 第二步:验证基础表结构'; SELECT COUNT(*) INTO table_count FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('ak_messages', 'ak_message_types', 'ak_message_recipients'); IF table_count < 3 THEN RAISE EXCEPTION '❌ 基础消息表不存在,请先执行 message_system.sql'; END IF; RAISE NOTICE '✅ 基础表结构验证通过 (% 个表)', table_count; END $$; -- 第三步:部署角色管理系统 DO $$ BEGIN RAISE NOTICE '📋 第三步:部署角色管理系统'; END $$; -- 清理现有角色管理组件 DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; DROP TRIGGER IF EXISTS trigger_update_user_roles_updated_at ON public.user_roles; DROP FUNCTION IF EXISTS public.handle_new_user() CASCADE; DROP FUNCTION IF EXISTS public.update_user_roles_updated_at() CASCADE; DROP FUNCTION IF EXISTS public.update_user_role(UUID, TEXT, UUID, JSONB) CASCADE; DROP FUNCTION IF EXISTS public.get_user_role(UUID) CASCADE; DROP FUNCTION IF EXISTS public.batch_update_user_roles(JSONB, UUID) CASCADE; DROP FUNCTION IF EXISTS public.sync_user_role_metadata(UUID) CASCADE; DROP FUNCTION IF EXISTS public.user_has_permission(TEXT, UUID) CASCADE; DROP FUNCTION IF EXISTS public.can_access_resource(TEXT, UUID, TEXT, UUID) CASCADE; DROP VIEW IF EXISTS public.user_roles_detailed CASCADE; -- 创建用户角色管理表 CREATE TABLE IF NOT EXISTS public.user_roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, role TEXT NOT NULL CHECK (role IN ('admin', 'teacher', 'student')), class_id UUID DEFAULT NULL, school_id UUID DEFAULT NULL, department TEXT DEFAULT NULL, permissions JSONB DEFAULT '{}'::jsonb, is_active BOOLEAN DEFAULT true, expires_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_by UUID REFERENCES auth.users(id), updated_by UUID REFERENCES auth.users(id), UNIQUE(user_id) ); -- 创建索引 CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON public.user_roles(user_id); CREATE INDEX IF NOT EXISTS idx_user_roles_role ON public.user_roles(role); CREATE INDEX IF NOT EXISTS idx_user_roles_active ON public.user_roles(is_active) WHERE is_active = true; -- 创建核心函数 CREATE OR REPLACE FUNCTION public.update_user_roles_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); NEW.updated_by = COALESCE(auth.uid(), NEW.updated_by); RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER trigger_update_user_roles_updated_at BEFORE UPDATE ON public.user_roles FOR EACH ROW EXECUTE FUNCTION public.update_user_roles_updated_at(); -- 同步用户角色到元数据 CREATE OR REPLACE FUNCTION public.sync_user_role_metadata(target_user_id UUID) RETURNS BOOLEAN AS $$ DECLARE user_role_record RECORD; BEGIN SELECT role, class_id, school_id, department, permissions, is_active INTO user_role_record FROM public.user_roles WHERE user_id = target_user_id AND is_active = true; IF FOUND THEN UPDATE auth.users SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) || jsonb_build_object( 'user_role', user_role_record.role, 'class_id', user_role_record.class_id, 'school_id', user_role_record.school_id, 'department', user_role_record.department, 'permissions', user_role_record.permissions, 'role_synced_at', extract(epoch from now()) ) WHERE id = target_user_id; RETURN true; ELSE UPDATE auth.users SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) || jsonb_build_object('user_role', 'inactive', 'role_synced_at', extract(epoch from now())) WHERE id = target_user_id; RETURN false; END IF; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 自动角色分配 CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS TRIGGER AS $$ DECLARE user_role TEXT := 'student'; user_domain TEXT; BEGIN user_domain := split_part(NEW.email, '@', 2); CASE WHEN user_domain IN ('teacher.edu', 'faculty.edu', 'staff.edu') THEN user_role := 'teacher'; WHEN user_domain IN ('admin.edu', 'management.edu') THEN user_role := 'admin'; WHEN NEW.email LIKE '%admin%' OR NEW.email LIKE '%manager%' THEN user_role := 'admin'; WHEN NEW.email LIKE '%teacher%' OR NEW.email LIKE '%faculty%' THEN user_role := 'teacher'; ELSE user_role := 'student'; END CASE; INSERT INTO public.user_roles (user_id, role, created_by) VALUES (NEW.id, user_role, NEW.id); PERFORM public.sync_user_role_metadata(NEW.id); RETURN NEW; EXCEPTION WHEN OTHERS THEN RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); -- 获取用户角色 CREATE OR REPLACE FUNCTION public.get_user_role(target_user_id UUID DEFAULT auth.uid()) RETURNS TEXT AS $$ DECLARE user_role TEXT; cached_role TEXT; role_synced_at NUMERIC; BEGIN IF target_user_id IS NULL THEN RETURN 'anonymous'; END IF; SELECT raw_user_meta_data->>'user_role', (raw_user_meta_data->>'role_synced_at')::numeric INTO cached_role, role_synced_at FROM auth.users WHERE id = target_user_id; IF cached_role IS NOT NULL AND role_synced_at IS NOT NULL AND (extract(epoch from now()) - role_synced_at) < 300 THEN RETURN cached_role; END IF; SELECT role INTO user_role FROM public.user_roles WHERE user_id = target_user_id AND is_active = true; IF FOUND THEN PERFORM public.sync_user_role_metadata(target_user_id); RETURN user_role; ELSE RETURN 'student'; END IF; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 权限检查函数 CREATE OR REPLACE FUNCTION public.user_has_permission( permission_name TEXT, target_user_id UUID DEFAULT auth.uid() ) RETURNS BOOLEAN AS $$ DECLARE user_role TEXT; user_permissions JSONB; BEGIN SELECT role, permissions INTO user_role, user_permissions FROM public.user_roles WHERE user_id = target_user_id AND is_active = true; IF user_role = 'admin' THEN RETURN true; END IF; CASE user_role WHEN 'teacher' THEN IF permission_name IN ('send_message', 'view_student_messages', 'create_group') THEN RETURN true; END IF; WHEN 'student' THEN IF permission_name IN ('send_message', 'view_own_messages') THEN RETURN true; END IF; END CASE; IF user_permissions IS NOT NULL THEN RETURN (user_permissions->permission_name)::boolean = true; END IF; RETURN false; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 第四步:配置权限策略 DO $$ DECLARE r RECORD; BEGIN RAISE NOTICE '📋 第四步:配置权限策略'; -- 清理现有策略 FOR r IN SELECT schemaname, tablename, policyname FROM pg_policies WHERE schemaname = 'public' AND tablename IN ('ak_messages', 'ak_message_recipients', 'ak_message_groups', 'ak_message_group_members', 'ak_message_templates', 'ak_user_message_preferences', 'ak_message_stats', 'ak_message_types') LOOP EXECUTE format('DROP POLICY IF EXISTS %I ON %I.%I', r.policyname, r.schemaname, r.tablename); END LOOP; END $$; -- 启用RLS ALTER TABLE public.ak_message_types ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_messages ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_message_recipients ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_message_groups ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_message_group_members ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_message_templates ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_user_message_preferences ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_message_stats ENABLE ROW LEVEL SECURITY; ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY; -- 核心权限策略 CREATE POLICY "users_can_view_relevant_messages" ON public.ak_messages FOR SELECT USING ( public.get_user_role(auth.uid()) = 'admin' OR (sender_type = 'user' AND sender_id = auth.uid()) OR (receiver_type = 'user' AND receiver_id = auth.uid()) OR (receiver_type = 'group' AND receiver_id IN ( SELECT group_id FROM public.ak_message_group_members WHERE user_id = auth.uid() AND status = 'active' )) OR (receiver_type = 'broadcast') ); CREATE POLICY "users_can_send_messages" ON public.ak_messages FOR INSERT WITH CHECK ( (auth.role() = 'authenticated' AND sender_type = 'user' AND sender_id = auth.uid()) OR (public.get_user_role(auth.uid()) = 'admin' AND sender_type = 'system') ); CREATE POLICY "users_can_view_own_role" ON public.user_roles FOR SELECT USING ( auth.uid() = user_id OR public.get_user_role(auth.uid()) = 'admin' ); CREATE POLICY "admins_can_manage_all_roles" ON public.user_roles FOR ALL USING (public.get_user_role(auth.uid()) = 'admin'); -- 第五步:插入测试数据 DO $$ BEGIN RAISE NOTICE '📋 第五步:插入测试数据'; END $$; -- 插入测试用户角色 INSERT INTO public.user_roles (user_id, role, department, permissions, created_by) VALUES ( '7bf7378e-a027-473e-97ac-3460ed3f170a', 'teacher', 'Computer Science', '{"can_create_groups": true, "can_send_broadcasts": false, "can_moderate": true}'::jsonb, '7bf7378e-a027-473e-97ac-3460ed3f170a' ), ( 'eed3824b-bba1-4309-8048-19d17367c084', 'student', 'Computer Science', '{"can_create_groups": false, "can_send_broadcasts": false}'::jsonb, 'eed3824b-bba1-4309-8048-19d17367c084' ) ON CONFLICT (user_id) DO UPDATE SET role = EXCLUDED.role, department = EXCLUDED.department, permissions = EXCLUDED.permissions, updated_at = NOW(); -- 同步角色到元数据 SELECT public.sync_user_role_metadata('7bf7378e-a027-473e-97ac-3460ed3f170a'); SELECT public.sync_user_role_metadata('eed3824b-bba1-4309-8048-19d17367c084'); -- 插入基础消息类型(如果不存在) INSERT INTO public.ak_message_types (type_name, display_name, description, icon, color, is_active) VALUES ('announcement', '公告', '重要公告消息', 'megaphone', '#FF6B6B', true), ('assignment', '作业', '作业相关消息', 'book', '#4ECDC4', true), ('notification', '通知', '一般通知消息', 'bell', '#45B7D1', true), ('reminder', '提醒', '提醒类消息', 'clock', '#96CEB4', true), ('urgent', '紧急', '紧急重要消息', 'alert-triangle', '#FFEAA7', true) ON CONFLICT (type_name) DO NOTHING; -- 插入示例消息 INSERT INTO public.ak_messages ( message_type_id, sender_type, sender_id, receiver_type, receiver_id, title, content, metadata, created_by ) VALUES ( (SELECT id FROM public.ak_message_types WHERE type_name = 'announcement' LIMIT 1), 'user', '7bf7378e-a027-473e-97ac-3460ed3f170a', 'user', 'eed3824b-bba1-4309-8048-19d17367c084', '欢迎使用消息系统', '这是一条测试消息,用于验证系统功能是否正常。', '{"test_data": true, "priority": "normal"}'::jsonb, '7bf7378e-a027-473e-97ac-3460ed3f170a' ); -- 第六步:创建管理视图 CREATE OR REPLACE VIEW public.user_roles_detailed AS SELECT ur.id, ur.user_id, ur.role, ur.class_id, ur.school_id, ur.department, ur.permissions, ur.is_active, ur.created_at, ur.updated_at, au.email, au.last_sign_in_at, au.raw_user_meta_data->>'user_role' as metadata_role, CASE WHEN ur.role = au.raw_user_meta_data->>'user_role' THEN true ELSE false END as role_synced FROM public.user_roles ur LEFT JOIN auth.users au ON au.id = ur.user_id; -- 第七步:创建测试函数 CREATE OR REPLACE FUNCTION public.test_deployment() RETURNS TABLE( test_name TEXT, status TEXT, result TEXT ) AS $$ BEGIN -- 测试表结构 RETURN QUERY SELECT 'Table Structure'::TEXT, CASE WHEN COUNT(*) >= 8 THEN '✅ PASS' ELSE '❌ FAIL' END, format('%s tables found', COUNT(*)) FROM information_schema.tables WHERE table_schema = 'public' AND table_name LIKE 'ak_%'; -- 测试角色系统 RETURN QUERY SELECT 'Role System'::TEXT, CASE WHEN COUNT(*) > 0 THEN '✅ PASS' ELSE '❌ FAIL' END, format('%s roles configured', COUNT(*)) FROM public.user_roles; -- 测试权限策略 RETURN QUERY SELECT 'RLS Policies'::TEXT, CASE WHEN COUNT(*) >= 3 THEN '✅ PASS' ELSE '❌ FAIL' END, format('%s policies active', COUNT(*)) FROM pg_policies WHERE schemaname = 'public' AND tablename LIKE 'ak_%'; -- 测试函数 RETURN QUERY SELECT 'Core Functions'::TEXT, CASE WHEN COUNT(*) >= 3 THEN '✅ PASS' ELSE '❌ FAIL' END, format('%s functions available', COUNT(*)) FROM information_schema.routines WHERE routine_schema = 'public' AND routine_name IN ('get_user_role', 'user_has_permission', 'sync_user_role_metadata'); END; $$ LANGUAGE plpgsql; -- 第八步:执行部署验证 DO $$ DECLARE test_result RECORD; BEGIN RAISE NOTICE '📋 第八步:执行部署验证'; FOR test_result IN SELECT * FROM public.test_deployment() LOOP RAISE NOTICE '🧪 %: % - %', test_result.test_name, test_result.status, test_result.result; END LOOP; END $$; -- 完成部署 DO $$ BEGIN RAISE NOTICE '🎉 Supabase消息系统部署完成!'; RAISE NOTICE '📊 系统统计:'; RAISE NOTICE ' - 用户角色: % 个', (SELECT COUNT(*) FROM public.user_roles); RAISE NOTICE ' - 消息类型: % 个', (SELECT COUNT(*) FROM public.ak_message_types); RAISE NOTICE ' - 权限策略: % 个', (SELECT COUNT(*) FROM pg_policies WHERE schemaname = 'public' AND tablename LIKE 'ak_%'); RAISE NOTICE ' - 核心函数: % 个', (SELECT COUNT(*) FROM information_schema.routines WHERE routine_schema = 'public' AND routine_name LIKE '%user%role%'); RAISE NOTICE '🚀 系统已就绪,可以开始使用!'; RAISE NOTICE '📝 测试账户:'; RAISE NOTICE ' - 教师: 7bf7378e-a027-473e-97ac-3460ed3f170a'; RAISE NOTICE ' - 学生: eed3824b-bba1-4309-8048-19d17367c084'; END $$; -- 运行最终测试 SELECT '🎯 部署验证结果' as section, test_name, status, result FROM public.test_deployment() UNION ALL SELECT '👥 用户角色统计' as section, role as test_name, '📊 COUNT' as status, COUNT(*)::TEXT as result FROM public.user_roles GROUP BY role ORDER BY section, test_name;