v1.4.0: Major Skills Expansion - 75 Total Skills
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "supabase-tools",
|
||||
"version": "2.0.3",
|
||||
"description": "Official Supabase MCP for database management with OAuth authentication.",
|
||||
"author": {
|
||||
"name": "Fatih Akyon"
|
||||
},
|
||||
"homepage": "https://github.com/fcakyon/claude-codex-settings#plugins",
|
||||
"repository": "https://github.com/fcakyon/claude-codex-settings",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"supabase": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.supabase.com/mcp?project_ref=REPLACE_WITH_PROJECT_REF&read_only=true"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
description: Configure Supabase MCP with OAuth authentication
|
||||
---
|
||||
|
||||
# Supabase Tools Setup
|
||||
|
||||
**Source:** [supabase-community/supabase-mcp](https://github.com/supabase-community/supabase-mcp)
|
||||
|
||||
Configure the official Supabase MCP server with OAuth.
|
||||
|
||||
## Step 1: Check Current Status
|
||||
|
||||
Read the MCP configuration from `${CLAUDE_PLUGIN_ROOT}/.mcp.json`.
|
||||
|
||||
Check if Supabase is configured:
|
||||
|
||||
- If `supabase.url` contains `REPLACE_WITH_PROJECT_REF`, it needs configuration
|
||||
- If it contains an actual project reference, already configured
|
||||
|
||||
Report status:
|
||||
|
||||
- "Supabase MCP is not configured - needs project reference"
|
||||
- OR "Supabase MCP is configured with project: PROJECT_REF"
|
||||
|
||||
## Step 2: Show Setup Guide
|
||||
|
||||
Tell the user:
|
||||
|
||||
```
|
||||
To configure Supabase MCP, you need your Supabase project reference.
|
||||
|
||||
Quick steps:
|
||||
1. Go to supabase.com/dashboard
|
||||
2. Select your project
|
||||
3. Go to Project Settings > General
|
||||
4. Copy the "Reference ID" (looks like: abcdefghijklmnop)
|
||||
|
||||
The MCP uses OAuth - you'll authenticate via browser when first connecting.
|
||||
```
|
||||
|
||||
## Step 3: Ask for Project Reference
|
||||
|
||||
Use AskUserQuestion:
|
||||
|
||||
- question: "Do you have your Supabase project reference ready?"
|
||||
- header: "Project Ref"
|
||||
- options:
|
||||
- label: "Yes, I have it"
|
||||
description: "I have my Supabase project reference ready"
|
||||
- label: "No, skip for now"
|
||||
description: "I'll configure it later"
|
||||
|
||||
If user selects "No, skip for now":
|
||||
|
||||
- Tell them they can run `/supabase-tools:setup` again when ready
|
||||
- Remind them they can disable Supabase MCP via `/mcp` if not needed
|
||||
- Exit
|
||||
|
||||
If user selects "Yes" or provides reference via "Other":
|
||||
|
||||
- If they provided reference in "Other" response, use that
|
||||
- Otherwise, ask them to paste the project reference
|
||||
|
||||
## Step 4: Validate Reference
|
||||
|
||||
Validate the provided reference:
|
||||
|
||||
- Must be alphanumeric
|
||||
- Should be 16-24 characters
|
||||
|
||||
If invalid:
|
||||
|
||||
- Show error: "Invalid project reference format"
|
||||
- Ask if they want to try again or skip
|
||||
|
||||
## Step 5: Update Configuration
|
||||
|
||||
1. Read current `${CLAUDE_PLUGIN_ROOT}/.mcp.json`
|
||||
2. Create backup at `${CLAUDE_PLUGIN_ROOT}/.mcp.json.backup`
|
||||
3. Replace `REPLACE_WITH_PROJECT_REF` with the actual project reference in the URL
|
||||
4. Write updated configuration back to `${CLAUDE_PLUGIN_ROOT}/.mcp.json`
|
||||
|
||||
## Step 6: Confirm Success
|
||||
|
||||
Tell the user:
|
||||
|
||||
```
|
||||
Supabase MCP configured successfully!
|
||||
|
||||
IMPORTANT: Restart Claude Code for changes to take effect.
|
||||
- Exit Claude Code
|
||||
- Run `claude` again
|
||||
|
||||
On first use, you'll be prompted to authenticate via browser (OAuth).
|
||||
To verify after restart, run /mcp and check that 'supabase' server is connected.
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: setup
|
||||
description: This skill should be used when user encounters "Supabase MCP error", "Supabase auth failed", "Supabase OAuth error", "Supabase not working", or needs help configuring Supabase integration.
|
||||
---
|
||||
|
||||
# Supabase Tools Setup
|
||||
|
||||
Run `/supabase-tools:setup` to configure Supabase MCP.
|
||||
|
||||
## Quick Fixes
|
||||
|
||||
- **OAuth failed** - Re-authenticate via Supabase dashboard
|
||||
- **Project not found** - Verify project_ref in config
|
||||
- **Permission denied** - Check RLS policies
|
||||
|
||||
## Don't Need Supabase?
|
||||
|
||||
Disable via `/mcp` command to prevent errors.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: supabase-usage
|
||||
description: This skill should be used when user asks to "query Supabase", "list Supabase tables", "get Supabase schema", "search Supabase records", "check Supabase database", "Supabase auth", "Supabase authentication", "RLS policy", "row level security", "Supabase foreign key", "table relationships", "Supabase join", "Supabase filter", "Supabase pagination", or needs guidance on Supabase database patterns, auth flows, RLS policies, or query best practices.
|
||||
---
|
||||
|
||||
# Supabase Database Patterns
|
||||
|
||||
Patterns for working with Supabase databases including Auth, Row Level Security, table relationships, and query best practices.
|
||||
|
||||
## Overview
|
||||
|
||||
- **MCP Tools**: Query and explore database structure
|
||||
- **Authentication**: User management, sessions, auth tables
|
||||
- **Row Level Security**: Policy patterns for data access control
|
||||
- **Table Relationships**: Foreign keys, joins, nested queries
|
||||
- **Query Patterns**: Filtering, pagination, performance
|
||||
|
||||
## MCP Tools
|
||||
|
||||
Available tools for database exploration:
|
||||
|
||||
- `mcp__supabase__list_tables` - List all tables in the database
|
||||
- `mcp__supabase__get_table_schema` - Get schema for a specific table
|
||||
- `mcp__supabase__execute_sql` - Run read-only SQL queries
|
||||
|
||||
**Workflow:**
|
||||
|
||||
1. Start with `list_tables` to understand database structure
|
||||
2. Use `get_table_schema` to inspect columns and types
|
||||
3. Use `execute_sql` for custom queries (read-only)
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### DO
|
||||
|
||||
- ✓ Enable RLS on all public tables
|
||||
- ✓ Use `(select auth.uid())` in RLS policies for performance
|
||||
- ✓ Add indexes on RLS-checked columns
|
||||
- ✓ Specify roles with `TO authenticated` in policies
|
||||
- ✓ Use `on delete cascade` for foreign keys to auth.users
|
||||
- ✓ Use cursor-based pagination for large datasets
|
||||
- ✓ Select only needed columns: `.select('id, name')` not `.select('*')`
|
||||
|
||||
### DON'T
|
||||
|
||||
- ✗ Store sensitive data without RLS
|
||||
- ✗ Use `auth.uid()` directly in policies (use `(select auth.uid())`)
|
||||
- ✗ Create policies without specifying roles
|
||||
- ✗ Forget indexes on frequently filtered columns
|
||||
- ✗ Use offset pagination for deep pages (>1000 rows)
|
||||
- ✗ Expose auth.users directly via API (use public profiles table)
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Common Filters
|
||||
|
||||
| Filter | JavaScript | Python |
|
||||
| ---------------- | ------------------------ | ------------------------ |
|
||||
| Equals | `.eq('col', val)` | `.eq("col", val)` |
|
||||
| Not equals | `.neq('col', val)` | `.neq("col", val)` |
|
||||
| Greater than | `.gt('col', val)` | `.gt("col", val)` |
|
||||
| Greater or equal | `.gte('col', val)` | `.gte("col", val)` |
|
||||
| Less than | `.lt('col', val)` | `.lt("col", val)` |
|
||||
| Less or equal | `.lte('col', val)` | `.lte("col", val)` |
|
||||
| Pattern match | `.ilike('col', '%val%')` | `.ilike("col", "%val%")` |
|
||||
| In list | `.in('col', [a,b])` | `.in_("col", [a,b])` |
|
||||
| Is null | `.is('col', null)` | `.is_("col", "null")` |
|
||||
| OR | `.or('a.eq.1,b.eq.2')` | `.or_("a.eq.1,b.eq.2")` |
|
||||
|
||||
### Auth Tables Quick Reference
|
||||
|
||||
| Table | Key Columns |
|
||||
| ----------------- | ----------------------------------------------------------------- |
|
||||
| `auth.users` | id, email, phone, created_at, last_sign_in_at, raw_user_meta_data |
|
||||
| `auth.sessions` | id, user_id, created_at, updated_at |
|
||||
| `auth.identities` | id, user_id, provider, identity_data |
|
||||
|
||||
### RLS Policy Template
|
||||
|
||||
```sql
|
||||
create policy "policy_name" on table_name
|
||||
to authenticated -- or anon, or specific role
|
||||
for select -- select, insert, update, delete, or all
|
||||
using ( (select auth.uid()) = user_id )
|
||||
with check ( (select auth.uid()) = user_id ); -- for insert/update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For detailed patterns and code examples, consult:
|
||||
|
||||
- **`references/auth.md`** - Authentication with JS/Python SDK, user profiles
|
||||
- **`references/rls.md`** - Row Level Security policies and performance tips
|
||||
- **`references/relationships.md`** - Table relationships and nested queries
|
||||
- **`references/query-patterns.md`** - Filtering, pagination, counting, indexes
|
||||
@@ -0,0 +1,100 @@
|
||||
# Supabase Authentication
|
||||
|
||||
Supabase Auth provides user management with JWT-based sessions.
|
||||
|
||||
## Auth Tables
|
||||
|
||||
Key tables in the `auth` schema:
|
||||
|
||||
- `auth.users` - User accounts (id, email, phone, created_at, etc.)
|
||||
- `auth.sessions` - Active sessions
|
||||
- `auth.identities` - OAuth provider identities
|
||||
|
||||
## JavaScript SDK
|
||||
|
||||
```javascript
|
||||
// Initialize client
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
const supabase = createClient(url, anonKey);
|
||||
|
||||
// Sign up
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: "user@example.com",
|
||||
password: "securepassword",
|
||||
});
|
||||
|
||||
// Sign in
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email: "user@example.com",
|
||||
password: "securepassword",
|
||||
});
|
||||
|
||||
// Get current user
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
// Sign out
|
||||
await supabase.auth.signOut();
|
||||
|
||||
// Listen to auth state changes
|
||||
supabase.auth.onAuthStateChange((event, session) => {
|
||||
console.log("Auth event:", event);
|
||||
if (session) console.log("User ID:", session.user.id);
|
||||
});
|
||||
|
||||
// OAuth sign in
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: "google",
|
||||
});
|
||||
```
|
||||
|
||||
## Python SDK
|
||||
|
||||
```python
|
||||
from supabase import Client, create_client
|
||||
|
||||
supabase: Client = create_client(url, key)
|
||||
|
||||
# Sign up
|
||||
response = supabase.auth.sign_up({"email": "user@example.com", "password": "securepassword"})
|
||||
|
||||
# Sign in
|
||||
response = supabase.auth.sign_in_with_password({"email": "user@example.com", "password": "securepassword"})
|
||||
|
||||
# Get current user
|
||||
user = supabase.auth.get_user()
|
||||
|
||||
# Sign out
|
||||
supabase.auth.sign_out()
|
||||
```
|
||||
|
||||
## User Profile Table Pattern
|
||||
|
||||
Link a public profile table to auth.users:
|
||||
|
||||
```sql
|
||||
create table public.profiles (
|
||||
id uuid not null references auth.users on delete cascade,
|
||||
first_name text,
|
||||
last_name text,
|
||||
avatar_url text,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
alter table public.profiles enable row level security;
|
||||
|
||||
-- Auto-create profile on signup (trigger)
|
||||
create function public.handle_new_user()
|
||||
returns trigger as $$
|
||||
begin
|
||||
insert into public.profiles (id)
|
||||
values (new.id);
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
|
||||
create trigger on_auth_user_created
|
||||
after insert on auth.users
|
||||
for each row execute procedure public.handle_new_user();
|
||||
```
|
||||
@@ -0,0 +1,180 @@
|
||||
# Query Patterns
|
||||
|
||||
## Filtering
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
// Equality
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.eq("status", "active");
|
||||
|
||||
// Comparison
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.gte("age", 18)
|
||||
.lt("age", 65);
|
||||
|
||||
// Pattern matching
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.ilike("name", "%john%");
|
||||
|
||||
// IN operator
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.in("role", ["admin", "moderator"]);
|
||||
|
||||
// OR conditions
|
||||
const { data } = await supabase
|
||||
.from("posts")
|
||||
.select("*")
|
||||
.or("status.eq.featured,priority.gte.5");
|
||||
|
||||
// NOT
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.not("status", "eq", "banned");
|
||||
|
||||
// NULL check
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.is("deleted_at", null);
|
||||
|
||||
// Array contains
|
||||
const { data } = await supabase
|
||||
.from("posts")
|
||||
.select("*")
|
||||
.contains("tags", ["javascript"]);
|
||||
|
||||
// Full-text search
|
||||
const { data } = await supabase
|
||||
.from("posts")
|
||||
.select("*")
|
||||
.textSearch("content", "supabase & database");
|
||||
```
|
||||
|
||||
**Python:**
|
||||
|
||||
```python
|
||||
# Equality
|
||||
response = supabase.table("users").select("*").eq("status", "active").execute()
|
||||
|
||||
# Comparison
|
||||
response = supabase.table("users").select("*").gte("age", 18).lt("age", 65).execute()
|
||||
|
||||
# Pattern matching (case-insensitive)
|
||||
response = supabase.table("users").select("*").ilike("name", "%john%").execute()
|
||||
|
||||
# IN operator
|
||||
response = supabase.table("users").select("*").in_("role", ["admin", "moderator"]).execute()
|
||||
|
||||
# OR conditions
|
||||
response = supabase.table("posts").select("*").or_("status.eq.featured,priority.gte.5").execute()
|
||||
|
||||
# NOT
|
||||
response = supabase.table("users").select("*").neq("status", "banned").execute()
|
||||
|
||||
# NULL check
|
||||
response = supabase.table("users").select("*").is_("deleted_at", "null").execute()
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
**Offset-based (simple, less efficient for large datasets):**
|
||||
|
||||
```javascript
|
||||
// JavaScript
|
||||
const { data } = await supabase
|
||||
.from("posts")
|
||||
.select("*")
|
||||
.order("created_at", { ascending: false })
|
||||
.range(0, 9); // rows 0-9 (first 10)
|
||||
|
||||
// Next page
|
||||
const { data } = await supabase
|
||||
.from("posts")
|
||||
.select("*")
|
||||
.order("created_at", { ascending: false })
|
||||
.range(10, 19);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
response = supabase.table("posts").select("*").order("created_at", desc=True).range(0, 9).execute()
|
||||
```
|
||||
|
||||
**Cursor-based (efficient for large datasets):**
|
||||
|
||||
```javascript
|
||||
// JavaScript - use last item's id/timestamp as cursor
|
||||
const { data } = await supabase
|
||||
.from("posts")
|
||||
.select("*")
|
||||
.order("created_at", { ascending: false })
|
||||
.lt("created_at", lastTimestamp)
|
||||
.limit(10);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
response = (
|
||||
supabase.table("posts")
|
||||
.select("*")
|
||||
.order("created_at", desc=True)
|
||||
.lt("created_at", last_timestamp)
|
||||
.limit(10)
|
||||
.execute()
|
||||
)
|
||||
```
|
||||
|
||||
## Counting Rows
|
||||
|
||||
```javascript
|
||||
// JavaScript - exact count
|
||||
const { count } = await supabase
|
||||
.from("users")
|
||||
.select("*", { count: "exact", head: true })
|
||||
.eq("status", "active");
|
||||
|
||||
console.log(`Active users: ${count}`);
|
||||
```
|
||||
|
||||
```python
|
||||
# Python
|
||||
response = supabase.table("users").select("*", count="exact", head=True).eq("status", "active").execute()
|
||||
|
||||
print(f"Active users: {response.count}")
|
||||
```
|
||||
|
||||
## Index Recommendations
|
||||
|
||||
Add indexes for frequently filtered/sorted columns:
|
||||
|
||||
```sql
|
||||
-- Single column index
|
||||
create index idx_posts_status on posts (status);
|
||||
|
||||
-- Composite index for common filter combinations
|
||||
create index idx_posts_user_status on posts (user_id, status);
|
||||
|
||||
-- Partial index for specific conditions
|
||||
create index idx_active_posts on posts (created_at) where status = 'active';
|
||||
|
||||
-- Use index_advisor for recommendations
|
||||
select * from index_advisor('SELECT * FROM posts WHERE status = ''active'' ORDER BY created_at');
|
||||
```
|
||||
|
||||
## Query Performance Analysis
|
||||
|
||||
```sql
|
||||
-- Analyze query execution plan
|
||||
explain analyze select * from posts where status = 'active' order by created_at limit 10;
|
||||
```
|
||||
@@ -0,0 +1,69 @@
|
||||
# Table Relationships
|
||||
|
||||
## Foreign Key Setup
|
||||
|
||||
```sql
|
||||
-- One-to-many: user has many posts
|
||||
create table posts (
|
||||
id serial primary key,
|
||||
user_id uuid references auth.users on delete cascade,
|
||||
title text,
|
||||
content text
|
||||
);
|
||||
|
||||
-- Many-to-many: posts have many tags
|
||||
create table tags (
|
||||
id serial primary key,
|
||||
name text unique
|
||||
);
|
||||
|
||||
create table post_tags (
|
||||
post_id int references posts on delete cascade,
|
||||
tag_id int references tags on delete cascade,
|
||||
primary key (post_id, tag_id)
|
||||
);
|
||||
```
|
||||
|
||||
## Querying Relationships (JavaScript)
|
||||
|
||||
Supabase auto-detects relationships from foreign keys:
|
||||
|
||||
```javascript
|
||||
// One-to-many: get posts with author
|
||||
const { data: posts } = await supabase.from("posts").select(`
|
||||
id, title, content,
|
||||
author:users!user_id(id, email, full_name)
|
||||
`);
|
||||
|
||||
// Nested relations: posts with author and comments
|
||||
const { data: posts } = await supabase.from("posts").select(`
|
||||
id, title,
|
||||
author:users!user_id(id, email),
|
||||
comments(id, content, user:users(email))
|
||||
`);
|
||||
|
||||
// Many-to-many: posts with tags
|
||||
const { data: posts } = await supabase.from("posts").select(`
|
||||
id, title,
|
||||
tags:post_tags(tag:tags(name))
|
||||
`);
|
||||
|
||||
// Specify foreign key with !hint when ambiguous
|
||||
const { data } = await supabase.from("messages").select(`
|
||||
sender:users!sender_id(name),
|
||||
receiver:users!receiver_id(name)
|
||||
`);
|
||||
```
|
||||
|
||||
## Querying Relationships (Python)
|
||||
|
||||
```python
|
||||
# One-to-many with nested select
|
||||
response = supabase.table("posts").select("id, title, author:users!user_id(id, email)").execute()
|
||||
|
||||
# Multiple nested relations
|
||||
response = supabase.table("posts").select("id, title, comments(id, content, user:users(email))").execute()
|
||||
|
||||
# Many-to-many through junction table
|
||||
response = supabase.table("posts").select("id, title, tags:post_tags(tag:tags(name))").execute()
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
# Row Level Security (RLS)
|
||||
|
||||
RLS controls data access at the row level based on the authenticated user.
|
||||
|
||||
## Enabling RLS
|
||||
|
||||
```sql
|
||||
alter table public.posts enable row level security;
|
||||
```
|
||||
|
||||
## Policy Types
|
||||
|
||||
| Operation | Clause | Purpose |
|
||||
| --------- | ---------------------- | -------------------------------- |
|
||||
| SELECT | `using` | Filter which rows can be read |
|
||||
| INSERT | `with check` | Validate new rows |
|
||||
| UPDATE | `using` + `with check` | Filter + validate |
|
||||
| DELETE | `using` | Filter which rows can be deleted |
|
||||
|
||||
## Common Policy Patterns
|
||||
|
||||
**1. User owns row:**
|
||||
|
||||
```sql
|
||||
create policy "Users can view own data" on profiles
|
||||
to authenticated
|
||||
using ( (select auth.uid()) = user_id );
|
||||
|
||||
create policy "Users can update own data" on profiles
|
||||
to authenticated
|
||||
using ( (select auth.uid()) = user_id )
|
||||
with check ( (select auth.uid()) = user_id );
|
||||
```
|
||||
|
||||
**2. Public read, owner write:**
|
||||
|
||||
```sql
|
||||
create policy "Public read" on posts
|
||||
for select using (true);
|
||||
|
||||
create policy "Owner can modify" on posts
|
||||
for all to authenticated
|
||||
using ( (select auth.uid()) = author_id );
|
||||
```
|
||||
|
||||
**3. Team/organization access:**
|
||||
|
||||
```sql
|
||||
create policy "Team members can view" on documents
|
||||
to authenticated
|
||||
using (
|
||||
team_id in (
|
||||
select team_id from team_members
|
||||
where user_id = (select auth.uid())
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
**4. Role-based access:**
|
||||
|
||||
```sql
|
||||
create policy "Admins can do anything" on posts
|
||||
to authenticated
|
||||
using (
|
||||
exists (
|
||||
select 1 from users
|
||||
where id = (select auth.uid()) and role = 'admin'
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## RLS Performance Tips
|
||||
|
||||
**Always use `(select auth.uid())` instead of `auth.uid()`:**
|
||||
|
||||
```sql
|
||||
-- SLOW (recalculates per row)
|
||||
using ( auth.uid() = user_id )
|
||||
|
||||
-- FAST (calculates once, 99%+ improvement)
|
||||
using ( (select auth.uid()) = user_id )
|
||||
```
|
||||
|
||||
**Add indexes on RLS columns:**
|
||||
|
||||
```sql
|
||||
create index idx_posts_user_id on posts using btree (user_id);
|
||||
create index idx_documents_team_id on documents using btree (team_id);
|
||||
```
|
||||
|
||||
**Specify roles with `TO`:**
|
||||
|
||||
```sql
|
||||
-- Good: policy only applies to authenticated users
|
||||
create policy "..." on posts to authenticated using (...);
|
||||
|
||||
-- Bad: policy applies to all roles including anon
|
||||
create policy "..." on posts using (...);
|
||||
```
|
||||
|
||||
## Viewing Policies
|
||||
|
||||
```sql
|
||||
select schemaname, tablename, policyname, permissive, roles, cmd, qual, with_check
|
||||
from pg_policies
|
||||
where tablename = 'your_table';
|
||||
```
|
||||
Reference in New Issue
Block a user