Initial commit of akmon project
This commit is contained in:
308
supabase_auth_roles_setup.sql
Normal file
308
supabase_auth_roles_setup.sql
Normal file
@@ -0,0 +1,308 @@
|
||||
-- =====================================================
|
||||
-- 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;
|
||||
*/
|
||||
Reference in New Issue
Block a user