supabase
Implement Supabase database patterns for PhotoVault including RLS policies, TypeScript queries, and storage operations. Use when working with database schemas, Row Level Security, migrations, storage buckets, auth.uid() policies, or debugging empty query results. Includes PhotoVault table structure and client setup patterns.
安装与使用
请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。
查看来源 ↗使用指南技能说明
⚠️ MANDATORY WORKFLOW - DO NOT SKIP
When this skill activates, you MUST follow the expert workflow before writing any code:
-
Spawn Domain Expert using the Task tool with this prompt:
Read the expert prompt at: C:\Users\natha\Stone-Fence-Brain\VENTURES\PhotoVault\claude\experts\supabase-expert.md Then research the codebase and write an implementation plan to: docs/claude/plans/supabase-[task-name]-plan.md Task: [describe the user's request] -
Spawn QA Critic after expert returns, using Task tool:
Read the QA critic prompt at: C:\Users\natha\Stone-Fence-Brain\VENTURES\PhotoVault\claude\experts\qa-critic-expert.md Review the plan at: docs/claude/plans/supabase-[task-name]-plan.md Write critique to: docs/claude/plans/supabase-[task-name]-critique.md -
Present BOTH plan and critique to user - wait for approval before implementing
DO NOT read files and start coding. DO NOT rationalize that "this is simple." Follow the workflow.
Supabase Integration
Core Principles
RLS First, Always
Row Level Security is not optional. Every table in the public schema MUST have RLS enabled.
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
Key insight: Once RLS is enabled with no policies, the table is completely locked down. You then selectively grant access.
Type Safety is Non-Negotiable
npx supabase gen types typescript --project-id <project-id> > src/types/supabase.ts
import { createClient } from '@supabase/supabase-js'
import { Database } from '@/types/supabase'
const supabase = createClient<Database>(url, key)
Understand Your Three Clients
| Client | When to Use | RLS Behavior |
|---|---|---|
| Browser Client | Client components, user actions | Respects RLS based on auth.uid() |
| Server Client | Server components, API routes | Respects RLS based on auth.uid() |
| Admin Client | Webhooks, cron jobs, migrations | Bypasses RLS entirely |
Never use the admin client for user-facing operations.
Anti-Patterns
RLS Mistakes
Not wrapping auth.uid() for performance
-- WRONG: Calls auth.uid() for every row
CREATE POLICY "slow_policy" ON posts
FOR SELECT USING (auth.uid() = user_id);
-- RIGHT: Caches auth.uid() per statement
CREATE POLICY "fast_policy" ON posts
FOR SELECT USING ((SELECT auth.uid()) = user_id);
Permissive policies without thinking
-- WRONG: ALL authenticated users see ALL rows
CREATE POLICY "bad_policy" ON posts
FOR SELECT TO authenticated
USING (true);
Query Mistakes
Not handling join result types correctly
// WRONG: Assuming joins always return arrays
const client = gallery.clients[0]
// RIGHT: Check the type
const clientData = gallery.clients
const client = Array.isArray(clientData) ? clientData[0] : clientData
Not using .single() or .maybeSingle() appropriately
// WRONG: Returns array when you expect one item
const { data } = await supabase.from('users').select().eq('id', id)
// RIGHT: Use .single() for guaranteed single row
const { data } = await supabase.from('users').select().eq('id', id).single()
// RIGHT: Use .maybeSingle() when row might not exist
const { data } = await supabase.from('users').select().eq('email', email).maybeSingle()
RLS Policy Patterns
Users own their data
CREATE POLICY "users_own_data" ON user_data
FOR ALL TO authenticated
USING ((SELECT auth.uid()) = user_id)
WITH CHECK ((SELECT auth.uid()) = user_id);
Photographer → Client relationship (PhotoVault pattern)
CREATE POLICY "photographer_sees_clients" ON clients
FOR SELECT TO authenticated
USING (
photographer_id IN (
SELECT id FROM photographers
WHERE user_id = (SELECT auth.uid())
)
);
CREATE POLICY "client_own_data" ON clients
FOR SELECT TO authenticated
USING (user_id = (SELECT auth.uid()));
Role-based access
CREATE POLICY "admin_only" ON admin_settings
FOR ALL TO authenticated
USING (
EXISTS (
SELECT 1 FROM user_profiles
WHERE user_id = (SELECT auth.uid())
AND role = 'admin'
)
);
TypeScript Query Patterns
Fetch with joins
async function getGalleryWithDetails(galleryId: string) {
const { data, error } = await supabase
.from('photo_galleries')
.select(`
*,
clients (id, name, email),
gallery_photos (id, filename, thumbnail_url, original_url)
`)
.eq('id', galleryId)
.single()
if (error) throw error
if (!data) return null
const client = Array.isArray(data.clients)
? data.clients[0]
: data.clients
return { ...data, client, photos: data.gallery_photos ?? [] }
}
PhotoVault Configuration
Key Tables
| Table | Purpose | Key Columns |
|---|---|---|
user_profiles |
Extended user data | user_id, user_type, business_name |
photo_galleries |
Gallery metadata | photographer_id, client_id, status |
gallery_photos |
Individual photos | gallery_id, original_url, thumbnail_url |
clients |
Client records | photographer_id, user_id, email |
commission_payments |
Payment tracking | photographer_id, amount, status |
Client Setup
// Browser client (client components)
import { createBrowserClient } from '@supabase/ssr'
import { Database } from '@/types/supabase'
export function createClient() {
return createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
// Server client (server components, API routes)
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createServerSupabaseClient() {
const cookieStore = await cookies()
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() { return cookieStore.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
}
// Admin client (webhooks, cron - BYPASSES RLS)
import { createClient } from '@supabase/supabase-js'
export function createAdminClient() {
return createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
}
Known PhotoVault Gotchas
-
Primary gallery table is
photo_galleries(notgalleries) -
Join results: Single relations return objects, not arrays - always check
Array.isArray() -
Photo URLs: Some photos may be missing
original_url- fall back tothumbnail_url -
User types: Stored in
user_profiles.user_typeas 'photographer', 'client', or 'admin'
Debugging Checklist
- Is RLS enabled? Check in Supabase Dashboard → Table → RLS toggle
- Are there policies? Check Authentication → Policies
- Is the user authenticated? Check
auth.uid()is not null - Does the policy match? Test in SQL Editor with
SET ROLE authenticated - Are you using the right client? Browser vs Server vs Admin
- Check the Supabase logs for policy violations
Test RLS in SQL Editor:
SELECT set_config('request.jwt.claims', '{"sub": "user-uuid-here"}', true);
SET ROLE authenticated;
SELECT * FROM your_table;
RESET ROLE;
使用示例
查看来源仓库中的使用示例与配置说明。
查看来源 ↗来源历史
- 收录时间
- 2026-01-20
- 索引更新时间
- 2026-01-26
- Branch
- main
收录与同步日期属于本站记录。源码修改历史请查看来源仓库。
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!