Files
leopost/docs/DATABASE.md
Michele fd56b120b8 docs(01-02): document database schema and security
- Document plans and profiles tables with all columns
- Document RLS policies and performance notes
- Document helper functions with TypeScript examples
- Document triggers and migration options
- Include security notes and best practices

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 03:27:04 +01:00

3.8 KiB

Database Schema - Leopost

Overview

Leopost uses Supabase (PostgreSQL) with Row Level Security for multi-tenant data isolation.

Tables

plans

Subscription plan definitions.

Column Type Description
id UUID Primary key
name TEXT Unique identifier: 'free', 'creator', 'pro'
display_name TEXT English display name
display_name_it TEXT Italian display name
price_monthly INTEGER Price in cents (0, 1900, 4900)
features JSONB Feature limits and flags
created_at TIMESTAMPTZ Creation timestamp

Features JSONB structure:

{
  "posts_per_month": 10,
  "ai_models": ["gpt-4o-mini"],
  "social_accounts": 1,
  "image_generation": false,
  "automation": false
}

Plan tiers:

Plan Price Posts/Month AI Models Social Accounts Images Automation
Free 0 10 gpt-4o-mini 1 No No
Creator 19 EUR 50 gpt-4o-mini, gpt-4o, claude-3-5-sonnet 3 Yes Manual
Pro 49 EUR 200 All models + claude-opus-4 10 Yes Full

profiles

User profiles with tenant isolation.

Column Type Description
id UUID Primary key, references auth.users
tenant_id UUID Tenant isolation key (auto-generated)
plan_id UUID References plans.id, defaults to 'free'
email TEXT User email
full_name TEXT Optional display name
avatar_url TEXT Optional avatar URL
created_at TIMESTAMPTZ Creation timestamp
updated_at TIMESTAMPTZ Last update timestamp

Indexes:

  • idx_profiles_tenant_id - For tenant-scoped queries
  • idx_profiles_plan_id - For plan-based queries
  • idx_profiles_email - For email lookups

Row Level Security

CRITICAL: RLS is enabled on all tables. Never bypass RLS in client code.

plans

  • SELECT: Everyone (authenticated + anon) can read

profiles

  • SELECT: Users can only read their own profile
  • UPDATE: Users can only update their own profile
  • INSERT: System creates via trigger on signup

Performance note: All policies use (SELECT auth.uid()) subquery pattern for 99% performance improvement over bare auth.uid() calls.

Helper Functions

get_user_plan_features()

Returns JSONB of current user's plan features. Use for limit checking.

const { data } = await supabase.rpc('get_user_plan_features')
// Returns: { posts_per_month: 10, ai_models: [...], ... }

get_user_plan_name()

Returns TEXT of current user's plan name ('free', 'creator', 'pro').

const { data } = await supabase.rpc('get_user_plan_name')
// Returns: 'free' | 'creator' | 'pro'

Triggers

on_auth_user_created

Automatically creates a profile when a new user signs up via Supabase Auth.

  • Sets tenant_id to new UUID (multi-tenant isolation)
  • Sets plan_id to 'free' plan
  • Copies email, full_name, avatar_url from auth metadata

profiles_updated_at

Automatically updates updated_at timestamp on profile changes.

Running Migrations

Option 1: Supabase Dashboard

  1. Go to SQL Editor
  2. Paste migration content from supabase/migrations/001_initial_auth_setup.sql
  3. Run

Option 2: Supabase CLI

supabase db push

Option 3: Direct connection

psql $DATABASE_URL -f supabase/migrations/001_initial_auth_setup.sql

Security Notes

  • Never use service_role key in client code
  • Always verify RLS is enabled after schema changes
  • Use Supabase Security Advisor in dashboard before production
  • tenant_id is in profiles table, not JWT (simpler approach for v1)
  • All helper functions use SECURITY DEFINER to bypass RLS when appropriate
  • Migration: supabase/migrations/001_initial_auth_setup.sql
  • Seed: supabase/seed.sql