-- 本人は自分のレコードを全操作可CREATE POLICY "users: 本人は全列読み書き" ON public.users FOR ALL USING (auth.uid() = id);-- ログイン済み会員なら他のユーザーの行も SELECT 可(display_name 等を表示するため)CREATE POLICY "users: 他会員はdisplay_name/avatar_urlのみ読める" ON public.users FOR SELECT USING (auth.role() = 'authenticated');-- admin は全ユーザーを全操作可CREATE POLICY "users: adminは全件読み書き" ON public.users FOR ALL USING ( EXISTS (SELECT 1 FROM public.users u WHERE u.id
3つのポリシーが重なっています。PostgreSQL RLS はポリシーの OR 評価で動くため、どれか1つが許可すれば操作できます。
memberships テーブル — 「本人は読み取り」「admin は全件」
-- 自分の会員ステータスは自分だけ読めるCREATE POLICY "memberships: 本人は読み取り可" ON public.memberships FOR SELECT USING (auth.uid() = user_id);-- admin は全件操作可(Webhook 経由での更新も含む)CREATE POLICY "memberships: adminは全件読み書き" ON public.memberships FOR ALL USING ( EXISTS (SELECT 1 FROM public.users u WHERE u.id = auth.uid() AND u.role = 'admin') );
memberships は会員の有効・無効・プランを管理する最重要テーブルです。本人は自分のレコードを読むだけで書き込みはできません。Stripe Webhook による更新はアプリサーバーが Service Role Key を使って行います。
courses / lessons — 「active な会員のみ published コースを閲覧可」
-- active ステータスの会員のみ、公開済みコースを閲覧可CREATE POLICY "courses: 会員はpublishedのみ閲覧可" ON public.courses FOR SELECT USING ( is_published = true AND EXISTS ( SELECT 1 FROM public.memberships m WHERE m.user_id = auth.uid() AND m.status = 'active' ) );CREATE POLICY "courses: adminは全件読み書き" ON public.courses FOR ALL USING ( EXISTS (SELECT 1 FROM public.users u WHERE u
この設計で「未払い会員が直接 API を叩いてもコンテンツが返らない」ことを DB レベルで保証します。RLS があれば、API ルートのガードを書き忘れても、DB からデータが出ることはありません。
event_logs — 「INSERT は全員可」「SELECT は admin のみ」
-- 未ログインでも INSERT 可(アクセスログ・行動ログの収集)CREATE POLICY "event_logs: 全員INSERT可" ON public.event_logs FOR INSERT WITH CHECK (true);-- SELECT は admin のみ(ログの閲覧・集計)CREATE POLICY "event_logs: adminのみSELECT" ON public.event_logs FOR SELECT USING ( EXISTS (SELECT 1 FROM public.users u WHERE u.id = auth.uid() AND u.role = 'admin') );
WITH CHECK (true) は「どんな内容でも書き込み可」という意味です。INSERT を許可しつつ、SELECT は admin にしか許可していないため、蓄積したログが外部に漏れることはありません。
一方で plans テーブルは全公開が必要なため:
-- plans: 全員読み取り可(料金ページは anon からアクセスされる)CREATE POLICY "plans: 全員読み取り可" ON public.plans FOR SELECT USING (true);
USING (true) は「すべての行を SELECT 可」という意味です。pricing ページはログイン前のユーザーも閲覧するため、anon キーから料金プランのデータを取得できる必要があります。