Initial commit of akmon project
This commit is contained in:
456
deploy_complete_system.sql
Normal file
456
deploy_complete_system.sql
Normal file
@@ -0,0 +1,456 @@
|
||||
-- =====================================================
|
||||
-- 一键部署脚本 - 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;
|
||||
Reference in New Issue
Block a user