Files
akmon/deploy_complete_system.sql
2026-01-20 08:04:15 +08:00

457 lines
16 KiB
PL/PgSQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =====================================================
-- 一键部署脚本 - 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;