146 lines
5.5 KiB
PL/PgSQL
146 lines
5.5 KiB
PL/PgSQL
-- ============================================
|
||
-- 用户登录 / 注册 - 核心用户资料表结构(创建版 / Create-only)
|
||
-- ============================================
|
||
-- 用途:创建核心业务用户资料表(ak_users)及其相关函数和 RLS 策略。
|
||
-- 特点:
|
||
-- 1. 不做 DROP/DELETE/TRUNCATE(不清空数据)
|
||
-- 2. 通过 IF NOT EXISTS + 系统表判断,实现可重复执行
|
||
-- 3. 职责单一:只负责 ak_users,不涉及其他基础表
|
||
-- 4. 依赖:应在基础表(01_create_tables.sql)之后执行
|
||
-- ============================================
|
||
|
||
-- ============================================
|
||
-- 1. 业务用户资料表 ak_users
|
||
-- ============================================
|
||
|
||
CREATE TABLE IF NOT EXISTS public.ak_users (
|
||
id uuid primary key,
|
||
username text,
|
||
email text,
|
||
gender text,
|
||
birthday date,
|
||
height_cm numeric,
|
||
weight_kg numeric,
|
||
bio text,
|
||
avatar_url text,
|
||
preferred_language text,
|
||
role text,
|
||
school_id text,
|
||
grade_id text,
|
||
class_id text,
|
||
created_at timestamptz default now(),
|
||
updated_at timestamptz default now()
|
||
);
|
||
|
||
-- 中文注释
|
||
COMMENT ON TABLE public.ak_users IS '业务用户资料表(与 auth.users 一一对应)';
|
||
COMMENT ON COLUMN public.ak_users.id IS '用户ID(等于 auth.users.id)';
|
||
COMMENT ON COLUMN public.ak_users.username IS '用户名/昵称';
|
||
COMMENT ON COLUMN public.ak_users.email IS '邮箱';
|
||
COMMENT ON COLUMN public.ak_users.gender IS '性别';
|
||
COMMENT ON COLUMN public.ak_users.birthday IS '生日';
|
||
COMMENT ON COLUMN public.ak_users.height_cm IS '身高(厘米)';
|
||
COMMENT ON COLUMN public.ak_users.weight_kg IS '体重(公斤)';
|
||
COMMENT ON COLUMN public.ak_users.bio IS '个人简介';
|
||
COMMENT ON COLUMN public.ak_users.avatar_url IS '头像地址';
|
||
COMMENT ON COLUMN public.ak_users.preferred_language IS '偏好语言';
|
||
COMMENT ON COLUMN public.ak_users.role IS '角色(如 customer/merchant/admin 等)';
|
||
COMMENT ON COLUMN public.ak_users.school_id IS '学校ID(可选)';
|
||
COMMENT ON COLUMN public.ak_users.grade_id IS '年级ID(可选)';
|
||
COMMENT ON COLUMN public.ak_users.class_id IS '班级ID(可选)';
|
||
COMMENT ON COLUMN public.ak_users.created_at IS '创建时间';
|
||
COMMENT ON COLUMN public.ak_users.updated_at IS '更新时间';
|
||
|
||
-- 为 ak_users 添加 updated_at 触发器
|
||
-- 注意:通用函数 update_updated_at_column() 在 01_create_tables.sql 中创建
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_ak_users_updated_at') THEN
|
||
EXECUTE 'CREATE TRIGGER update_ak_users_updated_at BEFORE UPDATE ON public.ak_users FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column()';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- ============================================
|
||
-- 2. 行级安全策略(RLS)
|
||
-- ============================================
|
||
|
||
-- 启用 RLS
|
||
ALTER TABLE public.ak_users ENABLE ROW LEVEL SECURITY;
|
||
|
||
-- 仅允许本人读写自己的资料
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='ak_users' AND policyname='ak_users_self_select') THEN
|
||
EXECUTE 'CREATE POLICY "ak_users_self_select" ON public.ak_users FOR SELECT USING (auth.uid() = id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='ak_users' AND policyname='ak_users_self_insert') THEN
|
||
EXECUTE 'CREATE POLICY "ak_users_self_insert" ON public.ak_users FOR INSERT WITH CHECK (auth.uid() = id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='ak_users' AND policyname='ak_users_self_update') THEN
|
||
EXECUTE 'CREATE POLICY "ak_users_self_update" ON public.ak_users FOR UPDATE USING (auth.uid() = id)';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- ============================================
|
||
-- 3. 相关函数
|
||
-- ============================================
|
||
|
||
-- 函数1:手动初始化/更新用户资料(可选,供前端调用)
|
||
CREATE OR REPLACE FUNCTION public.upsert_user_profile(
|
||
p_user_id uuid,
|
||
p_email text,
|
||
p_username text default null
|
||
)
|
||
RETURNS public.ak_users
|
||
LANGUAGE plpgsql
|
||
SECURITY DEFINER
|
||
SET search_path = public
|
||
AS $$
|
||
DECLARE
|
||
v_username text := coalesce(p_username, split_part(p_email, '@', 1), 'user');
|
||
v_result public.ak_users;
|
||
BEGIN
|
||
-- 插入或更新用户资料
|
||
INSERT INTO public.ak_users (id, email, username)
|
||
VALUES (p_user_id, p_email, v_username)
|
||
ON CONFLICT (id) DO UPDATE
|
||
SET
|
||
email = excluded.email,
|
||
username = coalesce(excluded.username, ak_users.username)
|
||
RETURNING * INTO v_result;
|
||
|
||
RETURN v_result;
|
||
END;
|
||
$$;
|
||
|
||
-- 函数2:供 auth.users 触发器使用,自动创建用户资料
|
||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||
RETURNS trigger
|
||
LANGUAGE plpgsql
|
||
SECURITY DEFINER
|
||
SET search_path = public
|
||
AS $$
|
||
BEGIN
|
||
BEGIN
|
||
INSERT INTO public.ak_users (id, email, username)
|
||
VALUES (NEW.id, NEW.email, COALESCE(SPLIT_PART(NEW.email, '@', 1), 'user'))
|
||
ON CONFLICT (id) DO NOTHING;
|
||
EXCEPTION WHEN OTHERS THEN
|
||
-- 重要:不要因为业务表写入失败而阻断 auth.users 的注册事务
|
||
RAISE WARNING 'handle_new_user failed: %', SQLERRM;
|
||
END;
|
||
|
||
RETURN NEW;
|
||
END;
|
||
$$;
|
||
|
||
-- ============================================
|
||
-- 4. 函数授权
|
||
-- ============================================
|
||
|
||
-- upsert_user_profile 只允许已登录用户调用
|
||
REVOKE ALL ON FUNCTION public.upsert_user_profile(uuid, text, text) FROM PUBLIC;
|
||
GRANT EXECUTE ON FUNCTION public.upsert_user_profile(uuid, text, text) TO authenticated;
|
||
|
||
-- handle_new_user 是触发器函数,由系统内部调用,无需对任何角色授权
|