-- ===================================================== -- Supabase Auth 角色管理和权限配置 -- 适用于消息系统的教师/学生角色管理 -- ===================================================== -- 1. 创建用户角色管理表 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, -- 可选:学校关联 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_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_class_id ON public.user_roles(class_id); -- 2. 创建更新时间戳触发器 CREATE OR REPLACE FUNCTION public.update_user_roles_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- 删除现有触发器(如果存在) DROP TRIGGER IF EXISTS trigger_update_user_roles_updated_at ON public.user_roles; 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(); -- 3. 自动为新用户分配角色的函数 CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS TRIGGER AS $$ BEGIN -- 根据邮箱后缀或其他规则自动分配角色 DECLARE user_role TEXT := 'student'; -- 默认角色 user_email TEXT := NEW.email; BEGIN -- 根据邮箱判断角色(可根据实际需求调整) IF user_email LIKE '%@teacher.%' OR user_email LIKE '%@edu.%' THEN user_role := 'teacher'; ELSIF user_email LIKE '%@admin.%' THEN user_role := 'admin'; END IF; -- 插入角色记录 INSERT INTO public.user_roles (user_id, role, created_by) VALUES (NEW.id, user_role, NEW.id); -- 更新用户元数据中的角色信息 UPDATE auth.users SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) || jsonb_build_object('user_role', user_role) WHERE id = NEW.id; RETURN NEW; END; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 4. 创建新用户触发器 -- 删除现有触发器(如果存在) DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); -- 5. 手动更新用户角色的函数 -- 删除现有函数(如果存在) DROP FUNCTION IF EXISTS public.update_user_role(UUID, TEXT, UUID); CREATE OR REPLACE FUNCTION public.update_user_role( target_user_id UUID, new_role TEXT, operator_user_id UUID DEFAULT NULL ) RETURNS BOOLEAN AS $$ DECLARE operator_role TEXT; BEGIN -- 检查操作者权限(只有admin可以修改角色) IF operator_user_id IS NOT NULL THEN SELECT role INTO operator_role FROM public.user_roles WHERE user_id = operator_user_id; IF operator_role != 'admin' THEN RAISE EXCEPTION 'Only admins can update user roles'; END IF; END IF; -- 检查新角色是否有效 IF new_role NOT IN ('admin', 'teacher', 'student') THEN RAISE EXCEPTION 'Invalid role: %', new_role; END IF; -- 更新角色表 UPDATE public.user_roles SET role = new_role, updated_at = NOW() WHERE user_id = target_user_id; -- 同步更新auth.users的元数据 UPDATE auth.users SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) || jsonb_build_object('user_role', new_role) WHERE id = target_user_id; RETURN FOUND; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 6. 获取用户角色的函数 -- 删除现有函数(如果存在) DROP FUNCTION IF EXISTS public.get_user_role(UUID); CREATE OR REPLACE FUNCTION public.get_user_role(target_user_id UUID DEFAULT auth.uid()) RETURNS TEXT AS $$ DECLARE user_role TEXT; BEGIN SELECT role INTO user_role FROM public.user_roles WHERE user_roles.user_id = target_user_id; RETURN COALESCE(user_role, 'student'); END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- 7. RLS 策略 for user_roles 表 ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY; -- 用户可以查看自己的角色 CREATE POLICY "Users can view own role" ON public.user_roles FOR SELECT USING (auth.uid() = user_id); -- 管理员可以查看和管理所有角色 CREATE POLICY "Admins can manage all roles" ON public.user_roles FOR ALL USING ( EXISTS ( SELECT 1 FROM public.user_roles ur WHERE ur.user_id = auth.uid() AND ur.role = 'admin' ) ); -- 教师可以查看同班学生的角色(如果有班级系统) CREATE POLICY "Teachers can view students in same class" ON public.user_roles FOR SELECT USING ( EXISTS ( SELECT 1 FROM public.user_roles teacher_role WHERE teacher_role.user_id = auth.uid() AND teacher_role.role = 'teacher' AND teacher_role.class_id = user_roles.class_id ) ); -- 8. 预设测试用户角色 INSERT INTO public.user_roles (user_id, role, created_by) VALUES ( '7bf7378e-a027-473e-97ac-3460ed3f170a', -- 教师用户ID 'teacher', '7bf7378e-a027-473e-97ac-3460ed3f170a' ) ON CONFLICT (user_id) DO UPDATE SET role = EXCLUDED.role, updated_at = NOW(); INSERT INTO public.user_roles (user_id, role, created_by) VALUES ( 'eed3824b-bba1-4309-8048-19d17367c084', -- 学生用户ID 'student', 'eed3824b-bba1-4309-8048-19d17367c084' ) ON CONFLICT (user_id) DO UPDATE SET role = EXCLUDED.role, updated_at = NOW(); -- 9. 更新这些用户的auth元数据 UPDATE auth.users SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) || jsonb_build_object('user_role', 'teacher') WHERE id = '7bf7378e-a027-473e-97ac-3460ed3f170a'; UPDATE auth.users SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) || jsonb_build_object('user_role', 'student') WHERE id = 'eed3824b-bba1-4309-8048-19d17367c084'; -- 10. 创建角色管理视图(方便查询) CREATE OR REPLACE VIEW public.user_roles_with_email AS SELECT ur.id, ur.user_id, ur.role, ur.class_id, ur.school_id, ur.created_at, ur.updated_at, au.email, 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; -- 注意:视图不支持RLS策略,权限控制通过底层表 user_roles 实现 -- 视图的访问权限由底层表的策略控制 -- 11. 批量角色管理函数(仅管理员) -- 删除现有函数(如果存在) DROP FUNCTION IF EXISTS public.batch_update_user_roles(JSONB, UUID); CREATE OR REPLACE FUNCTION public.batch_update_user_roles( role_updates JSONB, -- [{"user_id": "uuid", "role": "teacher"}, ...] operator_user_id UUID DEFAULT auth.uid() ) RETURNS TABLE(user_id UUID, old_role TEXT, new_role TEXT, success BOOLEAN) AS $$ DECLARE update_record RECORD; operator_role TEXT; old_role_val TEXT; BEGIN -- 检查操作者权限 SELECT role INTO operator_role FROM public.user_roles WHERE user_roles.user_id = operator_user_id; IF operator_role != 'admin' THEN RAISE EXCEPTION 'Only admins can batch update user roles'; END IF; -- 处理每个更新请求 FOR update_record IN SELECT * FROM jsonb_to_recordset(role_updates) AS x(user_id UUID, role TEXT) LOOP -- 获取旧角色 SELECT ur.role INTO old_role_val FROM public.user_roles ur WHERE ur.user_id = update_record.user_id; -- 尝试更新 BEGIN PERFORM public.update_user_role( update_record.user_id, update_record.role, operator_user_id ); RETURN QUERY SELECT update_record.user_id, old_role_val, update_record.role, true; EXCEPTION WHEN OTHERS THEN RETURN QUERY SELECT update_record.user_id, old_role_val, update_record.role, false; END; END LOOP; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- ===================================================== -- 使用说明和示例 -- ===================================================== /* -- 示例1:手动更新用户角色(需要管理员权限) SELECT public.update_user_role( 'eed3824b-bba1-4309-8048-19d17367c084', -- 用户ID 'teacher', -- 新角色 auth.uid() -- 操作者ID(需要是admin) ); -- 示例2:查询用户角色 SELECT public.get_user_role('7bf7378e-a027-473e-97ac-3460ed3f170a'); -- 示例3:查看所有用户角色(管理员视图) SELECT * FROM public.user_roles_with_email ORDER BY created_at DESC; -- 示例4:批量更新角色 SELECT * FROM public.batch_update_user_roles( '[ {"user_id": "eed3824b-bba1-4309-8048-19d17367c084", "role": "teacher"}, {"user_id": "another-user-id", "role": "student"} ]'::jsonb ); -- 示例5:检查角色同步状态 SELECT user_id, email, role, metadata_role, role_synced FROM public.user_roles_with_email WHERE NOT role_synced; */