refactor: consolidate AGENTS.md and CLAUDE.md files, update tech stac… (#444)
* refactor: consolidate AGENTS.md and CLAUDE.md files, update tech stack and architecture details - Merged content from CLAUDE.md into AGENTS.md for better organization. - Updated tech stack section to reflect the current technologies used, including Next.js, Supabase, and Tailwind CSS. - Enhanced monorepo structure documentation with detailed directory purposes. - Streamlined multi-tenant architecture explanation and essential commands. - Added key patterns for naming conventions and server actions. - Removed outdated agent files related to Playwright and PostgreSQL, ensuring a cleaner codebase. - Bumped version to 2.23.7 to reflect changes.
This commit is contained in:
committed by
GitHub
parent
bebd56238b
commit
cfa137795b
@@ -1,265 +1,73 @@
|
||||
# Supabase Database Schema Management
|
||||
|
||||
This file contains guidance for working with database schemas, migrations, and Supabase development workflows.
|
||||
# Supabase Database
|
||||
|
||||
## Schema Organization
|
||||
|
||||
Schemas are organized in numbered files in the `schemas/` directory. Numbers are used to sort dependencies.
|
||||
Schemas in `schemas/` directory with numbered prefixes for dependency ordering.
|
||||
|
||||
Migrations are generated from schemas. If creating a new schema, the migration can be created using the exact same content.
|
||||
## Skills
|
||||
|
||||
If modifying an existing migration, use the `diff` command:
|
||||
For database implementation:
|
||||
- `/postgres-expert` - Schema design, RLS, migrations, testing
|
||||
|
||||
### 1. Creating new entities
|
||||
## Migration Workflow
|
||||
|
||||
When creating new entities (such as creating a new tabble), we can create a migration as is, just copying its content.
|
||||
### New Entities
|
||||
|
||||
```bash
|
||||
# Create new schema file
|
||||
touch apps/web/supabase/schemas/15-my-new-feature.sql
|
||||
# Create schema file
|
||||
touch schemas/20-feature.sql
|
||||
|
||||
# Create Migration
|
||||
pnpm --filter web run supabase migrations new my-new-feature
|
||||
# Create migration
|
||||
pnpm --filter web run supabase migrations new feature_name
|
||||
|
||||
# Copy content to migration
|
||||
cp apps/web/supabase/schemas/15-my-new-feature.sql apps/web/supabase/migrations/$(ls -t apps/web/supabase/migrations/ | head -n1)
|
||||
|
||||
# Apply migration
|
||||
pnpm --filter web supabase migrations up # alternatively reset db with pnpm supabase:web:reset
|
||||
|
||||
# Generate TypeScript types
|
||||
# Copy content, apply, generate types
|
||||
pnpm --filter web supabase migrations up
|
||||
pnpm supabase:web:typegen
|
||||
```
|
||||
|
||||
### 2. Modifying existing entities
|
||||
|
||||
When modifying existing entities (such ass adding a field to an existing table), we can use the `diff` command to generate a migration following the changes:
|
||||
### Modify Existing
|
||||
|
||||
```bash
|
||||
# Edit schema file (e.g., schemas/03-accounts.sql)
|
||||
# Make your changes...
|
||||
# Edit schema, generate diff
|
||||
pnpm --filter web run supabase:db:diff -f update_feature
|
||||
|
||||
# Create migration for changes
|
||||
pnpm --filter web run supabase:db:diff -f update-accounts
|
||||
|
||||
# Apply and test
|
||||
pnpm --filter web supabase migrations up # alternatively reset db with pnpm supabase:web:reset
|
||||
|
||||
# After resetting
|
||||
# Apply and regenerate
|
||||
pnpm --filter web supabase migrations up
|
||||
pnpm supabase:web:typegen
|
||||
```
|
||||
|
||||
## Security First Patterns
|
||||
## Security Rules
|
||||
|
||||
## Add permissions (if any)
|
||||
- **ALWAYS enable RLS** on new tables
|
||||
- **NEVER use SECURITY DEFINER** without explicit access controls
|
||||
- Use existing helper functions (see `/postgres-expert` skill)
|
||||
|
||||
## Table Template
|
||||
|
||||
```sql
|
||||
ALTER TYPE public.app_permissions ADD VALUE 'notes.manage';
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### Table Creation with RLS
|
||||
|
||||
```sql
|
||||
-- Create table
|
||||
create table if not exists public.notes (
|
||||
create table if not exists public.feature (
|
||||
id uuid unique not null default extensions.uuid_generate_v4(),
|
||||
account_id uuid references public.accounts(id) on delete cascade not null,
|
||||
-- ...
|
||||
created_at timestamp with time zone default now(),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
-- CRITICAL: Always enable RLS
|
||||
alter table "public"."notes" enable row level security;
|
||||
alter table "public"."feature" enable row level security;
|
||||
revoke all on public.feature from authenticated, service_role;
|
||||
grant select, insert, update, delete on table public.feature to authenticated;
|
||||
|
||||
-- Revoke default permissions
|
||||
revoke all on public.notes from authenticated, service_role;
|
||||
|
||||
-- Grant specific permissions
|
||||
grant select, insert, update, delete on table public.notes to authenticated;
|
||||
|
||||
-- Add RLS policies
|
||||
create policy "notes_read" on public.notes for select
|
||||
-- Use helper functions for policies
|
||||
create policy "feature_read" on public.feature for select
|
||||
to authenticated using (
|
||||
account_id = (select auth.uid()) or
|
||||
public.has_role_on_account(account_id)
|
||||
);
|
||||
|
||||
create policy "notes_write" on public.notes for insert
|
||||
to authenticated with check (
|
||||
public.has_permission(auth.uid(), account_id, 'notes.manage'::app_permissions)
|
||||
);
|
||||
|
||||
create policy "notes_update" on public.notes for update
|
||||
to authenticated using (
|
||||
public.has_permission(auth.uid(), account_id, 'notes.manage'::app_permissions)
|
||||
)
|
||||
with check (
|
||||
public.has_permission(auth.uid(), account_id, 'notes.manage'::app_permissions)
|
||||
);
|
||||
|
||||
create policy "notes_delete" on public.notes for delete
|
||||
to authenticated using (
|
||||
public.has_permission(auth.uid(), account_id, 'notes.manage'::app_permissions)
|
||||
);
|
||||
```
|
||||
|
||||
### Storage Bucket Policies
|
||||
|
||||
```sql
|
||||
-- Create storage bucket
|
||||
insert into storage.buckets (id, name, public)
|
||||
values ('documents', 'documents', false);
|
||||
|
||||
-- RLS policy for storage
|
||||
create policy documents_policy on storage.objects for all using (
|
||||
bucket_id = 'documents'
|
||||
and (
|
||||
-- File belongs to user's account
|
||||
kit.get_storage_filename_as_uuid(name) = auth.uid()
|
||||
or
|
||||
-- User has access to the account
|
||||
public.has_role_on_account(kit.get_storage_filename_as_uuid(name))
|
||||
)
|
||||
)
|
||||
with check (
|
||||
bucket_id = 'documents'
|
||||
and (
|
||||
kit.get_storage_filename_as_uuid(name) = auth.uid()
|
||||
or
|
||||
public.has_permission(
|
||||
auth.uid(),
|
||||
kit.get_storage_filename_as_uuid(name),
|
||||
'files.upload'::app_permissions
|
||||
)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Function Creation Patterns
|
||||
|
||||
### Safe Security Definer Functions
|
||||
|
||||
```sql
|
||||
-- NEVER create security definer functions without explicit access controls
|
||||
create or replace function public.create_team_account(account_name text)
|
||||
returns public.accounts
|
||||
language plpgsql
|
||||
security definer -- Elevated privileges
|
||||
set search_path = '' -- Prevent SQL injection
|
||||
as $$
|
||||
declare
|
||||
new_account public.accounts;
|
||||
begin
|
||||
-- CRITICAL: Validate permissions first
|
||||
if not public.is_set('enable_team_accounts') then
|
||||
raise exception 'Team accounts are not enabled';
|
||||
end if;
|
||||
|
||||
-- Additional validation can go here
|
||||
if length(account_name) < 3 then
|
||||
raise exception 'Account name must be at least 3 characters';
|
||||
end if;
|
||||
|
||||
-- Now safe to proceed with elevated privileges
|
||||
insert into public.accounts (name, is_personal_account)
|
||||
values (account_name, false)
|
||||
returning * into new_account;
|
||||
|
||||
return new_account;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Grant to authenticated users only
|
||||
grant execute on function public.create_team_account(text) to authenticated;
|
||||
```
|
||||
|
||||
### Security Invoker Functions (Safer)
|
||||
|
||||
```sql
|
||||
-- Preferred: Functions that inherit RLS policies
|
||||
create or replace function public.get_account_notes(target_account_id uuid)
|
||||
returns setof public.notes
|
||||
language plpgsql
|
||||
security invoker -- Inherits caller's permissions (RLS applies)
|
||||
set search_path = ''
|
||||
as $$
|
||||
begin
|
||||
-- RLS policies will automatically restrict results
|
||||
return query
|
||||
select * from public.notes
|
||||
where account_id = target_account_id
|
||||
order by created_at desc;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.get_account_notes(uuid) to authenticated;
|
||||
```
|
||||
|
||||
### Safe Column Additions
|
||||
|
||||
```sql
|
||||
-- Safe: Add nullable columns
|
||||
alter table public.accounts
|
||||
add column if not exists description text;
|
||||
|
||||
-- Safe: Add columns with defaults
|
||||
alter table public.accounts
|
||||
add column if not exists is_verified boolean default false not null;
|
||||
|
||||
-- Unsafe: Adding non-null columns without defaults
|
||||
-- alter table public.accounts add column required_field text not null; -- DON'T DO THIS
|
||||
```
|
||||
|
||||
### Index Management
|
||||
|
||||
```sql
|
||||
-- Create indexes concurrently for large tables
|
||||
create index concurrently if not exists ix_accounts_created_at
|
||||
on public.accounts (created_at desc);
|
||||
|
||||
-- Drop unused indexes
|
||||
drop index if exists ix_old_unused_index;
|
||||
```
|
||||
|
||||
## Testing Database Changes
|
||||
|
||||
### Local Testing
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Test with fresh database
|
||||
pnpm supabase:web:reset
|
||||
|
||||
# Test your changes
|
||||
pnpm run supabase:web:test
|
||||
```
|
||||
|
||||
## Common Schema Patterns
|
||||
|
||||
### Audit Trail
|
||||
|
||||
Add triggers if the properties exist and are appropriate:
|
||||
|
||||
- `public.trigger_set_timestamps()` - for tables with `created_at` and `updated_at`
|
||||
columns
|
||||
- `public.trigger_set_user_tracking()` - for tables with `created_by` and `updated_by`
|
||||
columns
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# View migration status
|
||||
pnpm --filter web supabase migrations list
|
||||
|
||||
# Reset database completely
|
||||
pnpm supabase:web:reset
|
||||
|
||||
# Generate migration from schema diff
|
||||
pnpm --filter web run supabase:db:diff -f migration-name
|
||||
|
||||
## Apply created migration
|
||||
pnpm --filter web supabase migrations up
|
||||
|
||||
# Apply specific migration
|
||||
pnpm --filter web supabase migrations up --include-schemas public
|
||||
pnpm supabase:web:reset # Reset database
|
||||
pnpm supabase:web:typegen # Generate TypeScript types
|
||||
pnpm --filter web supabase migrations list # View migrations
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user