feat(admin): complete decoration module database integration including DIY pages, RLS and RPCs
This commit is contained in:
32
docs/sql/10_schema/decoration/ak_diy_pages_v1.sql
Normal file
32
docs/sql/10_schema/decoration/ak_diy_pages_v1.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- =====================================================================================
|
||||
-- Schema: 装修模块 - DIY 页面配置表
|
||||
-- 位置:docs/sql/10_schema/decoration/ak_diy_pages_v1.sql
|
||||
-- 对象类型:TABLE
|
||||
-- 版本:v1
|
||||
-- 说明:存储首页、专题页及个人中心的 DIY 布局 JSON 配置
|
||||
-- =====================================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.ak_diy_pages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL, -- home: 首页, topic: 专题页, user: 个人中心
|
||||
|
||||
config JSONB NOT NULL DEFAULT '{}'::jsonb, -- 核心布局配置 (组件列表及参数)
|
||||
|
||||
is_home BOOLEAN NOT NULL DEFAULT FALSE, -- 是否为生效首页
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE, -- 是否启用
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_by UUID REFERENCES public.ak_users(id),
|
||||
updated_by UUID REFERENCES public.ak_users(id)
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX IF NOT EXISTS idx_diy_pages_type ON public.ak_diy_pages (type);
|
||||
CREATE INDEX IF NOT EXISTS idx_diy_pages_is_home ON public.ak_diy_pages (is_home) WHERE is_home = TRUE;
|
||||
|
||||
-- 注释
|
||||
COMMENT ON TABLE public.ak_diy_pages IS 'DIY 页面装修配置表';
|
||||
COMMENT ON COLUMN public.ak_diy_pages.type IS '页面类型: home(首页), topic(专题), user(个人中心)';
|
||||
COMMENT ON COLUMN public.ak_diy_pages.config IS 'DIY 布局配置 JSON';
|
||||
18
docs/sql/20_rls/decoration/ml_decoration_rls_v1.sql
Normal file
18
docs/sql/20_rls/decoration/ml_decoration_rls_v1.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- =====================================================================================
|
||||
-- RLS: 装修模块 - DIY 页面安全策略
|
||||
-- 位置:docs/sql/20_rls/decoration/ml_decoration_rls_v1.sql
|
||||
-- 对象类型:RLS 策略
|
||||
-- 版本:v1
|
||||
-- 说明:消费者端公开只读已发布的页面;管理端通过 SECURITY DEFINER RPC 进行管理
|
||||
-- =====================================================================================
|
||||
|
||||
-- 1. 启用 RLS
|
||||
ALTER TABLE public.ak_diy_pages ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- 2. 消费者端策略:允许匿名和登录用户读取已启用的页面
|
||||
DROP POLICY IF EXISTS diy_pages_select_active ON public.ak_diy_pages;
|
||||
CREATE POLICY diy_pages_select_active ON public.ak_diy_pages
|
||||
FOR SELECT TO anon, authenticated
|
||||
USING (is_active = true);
|
||||
|
||||
-- 管理端全量管理将通过 SECURITY DEFINER 的 RPC 接口执行,此处不再额外开放直接表操作
|
||||
40
docs/sql/30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql
Normal file
40
docs/sql/30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
-- RPC: rpc_admin_delete_diy_page
|
||||
-- 管理端删除 DIY 页面配置
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rpc_admin_delete_diy_page(
|
||||
p_id uuid
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_ok boolean;
|
||||
BEGIN
|
||||
-- 1. 权限检查 (仅管理员)
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM public.ak_users u
|
||||
WHERE u.id = auth.uid() AND u.role = 'admin'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'permission denied';
|
||||
END IF;
|
||||
|
||||
-- 2. 执行删除 (不允许删除当前生效的首页)
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.ak_diy_pages
|
||||
WHERE id = p_id AND is_home = true
|
||||
) THEN
|
||||
RAISE EXCEPTION 'cannot delete the active home page';
|
||||
END IF;
|
||||
|
||||
DELETE FROM public.ak_diy_pages WHERE id = p_id;
|
||||
|
||||
GET DIAGNOSTICS v_ok = ROW_COUNT;
|
||||
RETURN v_ok;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 授权
|
||||
REVOKE ALL ON FUNCTION public.rpc_admin_delete_diy_page(uuid) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.rpc_admin_delete_diy_page(uuid) TO authenticated;
|
||||
@@ -0,0 +1,60 @@
|
||||
-- RPC: rpc_admin_get_diy_page_list
|
||||
-- 管理端获取 DIY 页面分页列表
|
||||
-- 支持按名称搜索和按类型筛选
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rpc_admin_get_diy_page_list(
|
||||
p_search text DEFAULT NULL,
|
||||
p_type text DEFAULT NULL,
|
||||
p_page integer DEFAULT 1,
|
||||
p_page_size integer DEFAULT 20
|
||||
)
|
||||
RETURNS JSONB
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_page integer := GREATEST(1, COALESCE(p_page, 1));
|
||||
v_page_size integer := LEAST(200, GREATEST(1, COALESCE(p_page_size, 20)));
|
||||
v_offset integer := (v_page - 1) * v_page_size;
|
||||
v_total bigint;
|
||||
v_items jsonb;
|
||||
BEGIN
|
||||
-- 1. 权限检查 (仅管理员或分析员)
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM public.ak_users u
|
||||
WHERE u.id = auth.uid() AND u.role IN ('admin', 'analytics')
|
||||
) THEN
|
||||
RAISE EXCEPTION 'permission denied';
|
||||
END IF;
|
||||
|
||||
-- 2. 获取总数
|
||||
SELECT COUNT(*) INTO v_total
|
||||
FROM public.ak_diy_pages
|
||||
WHERE (p_search IS NULL OR p_search = '' OR name ILIKE '%' || p_search || '%')
|
||||
AND (p_type IS NULL OR type = p_type);
|
||||
|
||||
-- 3. 获取明细
|
||||
SELECT jsonb_agg(t) INTO v_items
|
||||
FROM (
|
||||
SELECT
|
||||
id, name, type, is_home, is_active,
|
||||
created_at, updated_at
|
||||
FROM public.ak_diy_pages
|
||||
WHERE (p_search IS NULL OR p_search = '' OR name ILIKE '%' || p_search || '%')
|
||||
AND (p_type IS NULL OR type = p_type)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT v_page_size OFFSET v_offset
|
||||
) t;
|
||||
|
||||
-- 4. 返回 JSON 结果
|
||||
RETURN jsonb_build_object(
|
||||
'total', v_total,
|
||||
'items', COALESCE(v_items, '[]'::jsonb)
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 授权
|
||||
REVOKE ALL ON FUNCTION public.rpc_admin_get_diy_page_list(text, text, integer, integer) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.rpc_admin_get_diy_page_list(text, text, integer, integer) TO authenticated;
|
||||
57
docs/sql/30_rpc/decoration/rpc_admin_save_diy_page_v1.sql
Normal file
57
docs/sql/30_rpc/decoration/rpc_admin_save_diy_page_v1.sql
Normal file
@@ -0,0 +1,57 @@
|
||||
-- RPC: rpc_admin_save_diy_page
|
||||
-- 管理端新增或更新 DIY 页面配置
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rpc_admin_save_diy_page(
|
||||
p_id uuid DEFAULT NULL,
|
||||
p_name text DEFAULT NULL,
|
||||
p_type text DEFAULT NULL,
|
||||
p_config jsonb DEFAULT '{}'::jsonb,
|
||||
p_is_active boolean DEFAULT true
|
||||
)
|
||||
RETURNS uuid
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_id uuid;
|
||||
BEGIN
|
||||
-- 1. 权限检查 (仅管理员)
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM public.ak_users u
|
||||
WHERE u.id = auth.uid() AND u.role = 'admin'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'permission denied';
|
||||
END IF;
|
||||
|
||||
-- 2. 新增或更新
|
||||
IF p_id IS NULL THEN
|
||||
INSERT INTO public.ak_diy_pages (
|
||||
name, type, config, is_active, updated_by, created_by
|
||||
) VALUES (
|
||||
p_name, p_type, p_config, p_is_active, auth.uid(), auth.uid()
|
||||
) RETURNING id INTO v_id;
|
||||
ELSE
|
||||
UPDATE public.ak_diy_pages
|
||||
SET
|
||||
name = COALESCE(p_name, name),
|
||||
type = COALESCE(p_type, type),
|
||||
config = COALESCE(p_config, config),
|
||||
is_active = COALESCE(p_is_active, is_active),
|
||||
updated_at = now(),
|
||||
updated_by = auth.uid()
|
||||
WHERE id = p_id
|
||||
RETURNING id INTO v_id;
|
||||
|
||||
IF v_id IS NULL THEN
|
||||
RAISE EXCEPTION 'page not found';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN v_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 授权
|
||||
REVOKE ALL ON FUNCTION public.rpc_admin_save_diy_page(uuid, text, text, jsonb, boolean) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.rpc_admin_save_diy_page(uuid, text, text, jsonb, boolean) TO authenticated;
|
||||
40
docs/sql/30_rpc/decoration/rpc_admin_set_home_page_v1.sql
Normal file
40
docs/sql/30_rpc/decoration/rpc_admin_set_home_page_v1.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
-- RPC: rpc_admin_set_home_page
|
||||
-- 管理端设置生效首页
|
||||
-- 逻辑:先取消所有同类型页面的 is_home 状态,再设置目标页面为 is_home
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rpc_admin_set_home_page(
|
||||
p_id uuid
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_type text;
|
||||
BEGIN
|
||||
-- 1. 权限检查 (仅管理员)
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM public.ak_users u
|
||||
WHERE u.id = auth.uid() AND u.role = 'admin'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'permission denied';
|
||||
END IF;
|
||||
|
||||
-- 2. 获取目标页面类型
|
||||
SELECT type INTO v_type FROM public.ak_diy_pages WHERE id = p_id;
|
||||
IF v_type IS NULL THEN
|
||||
RAISE EXCEPTION 'page not found';
|
||||
END IF;
|
||||
|
||||
-- 3. 原子切换:同一类型的页面只能有一个 is_home
|
||||
UPDATE public.ak_diy_pages SET is_home = false WHERE type = v_type;
|
||||
UPDATE public.ak_diy_pages SET is_home = true WHERE id = p_id;
|
||||
|
||||
RETURN true;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 授权
|
||||
REVOKE ALL ON FUNCTION public.rpc_admin_set_home_page(uuid) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.rpc_admin_set_home_page(uuid) TO authenticated;
|
||||
Reference in New Issue
Block a user