Back to catalog

supabase-crm

Supabase backend patterns for Crispy-CRM including RLS policies, Edge Functions, service layer architecture, and CRM business logic

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

---
name: supabase-crm
description: Supabase backend patterns for Crispy-CRM including RLS policies, Edge Functions, service layer architecture, and CRM business logic
---

# Supabase CRM Skill

## Purpose

This skill provides Supabase-specific backend patterns for the Crispy-CRM project, including:
- Service layer architecture with DataProvider abstraction
- Row Level Security (RLS) policies for multi-tenant access
- Edge Functions for complex business operations
- Database migrations and schema management
- Query optimization and performance patterns
- CRM-specific business logic (Organizations, Contacts, Opportunities)

## When to Use This Skill

**Auto-activates when:**
- Editing files in `src/services/`, `supabase/functions/`, or `supabase/migrations/`
- Working with Supabase queries, RLS policies, or Edge Functions
- Implementing business logic for Organizations, Contacts, or Opportunities
- Writing database migrations or TypeScript service classes
- Keywords: "supabase", "database", "query", "RLS", "edge function", "service layer"

**Use manually for:**
- Designing database schema changes
- Creating new service classes
- Implementing complex business rules
- Optimizing database queries
- Setting up RLS policies

## Engineering Constitution Alignment

This skill follows Crispy-CRM's Engineering Constitution:

1. **Validation at API boundaries** - Use Zod schemas for RPC validation
2. **Single source of truth** - DataProvider abstraction for all data access
3. **Service layer orchestration** - Business logic in service classes, not components
4. **TypeScript type safety** - Types inferred from Zod schemas
5. **Fail-fast validation** - Validate inputs before database operations

## Quick Reference

### Service Layer Pattern

```typescript
// Service classes extend DataProvider abstraction
export class OrganizationService {
  constructor(private dataProvider: DataProvider) {}

  async createOrganization(input: CreateOrgInput): Promise<Organization> {
    // 1. Validate at boundary
    const validated = CreateOrgSchema.parse(input);
    
    // 2. Apply business rules
    await this.validateHierarchy(validated);
    
    // 3. Use DataProvider (single source of truth)
    const { data } = await this.dataProvider.create('organizations', {
      data: validated
    });
    
    return data;
  }
}
```

### RLS Policy Pattern

```sql
-- Enable RLS on table
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;

-- Policy: Authenticated users can access all organizations (shared team model)
CREATE POLICY authenticated_select_organizations ON organizations
  FOR SELECT
  TO authenticated
  USING (deleted_at IS NULL);

-- Policy: Authenticated users can insert organizations
CREATE POLICY authenticated_insert_organizations ON organizations
  FOR INSERT
  TO authenticated
  WITH CHECK (true);
```

### Edge Function Pattern

```typescript
// Edge Function with validation and error handling
import { createClient } from '@supabase/supabase-js';
import { z } from 'zod';

const RequestSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  profile: z.object({
    first_name: z.string(),
    last_name: z.string()
  })
});

Deno.serve(async (req) => {
  try {
    // 1. Parse and validate input
    const body = await req.json();
    const validated = RequestSchema.parse(body);
    
    // 2. Initialize Supabase client with service role
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL')!,
      Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
    );
    
    // 3. Execute business logic
    const result = await createSalesUser(supabase, validated);
    
    return new Response(JSON.stringify(result), {
      headers: { 'Content-Type': 'application/json' },
      status: 200
    });
  } catch (error) {
    // 4. Consistent error handling
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 400, headers: { 'Content-Type': 'application/json' } }
    );
  }
});
```

### Migration Pattern

```sql
-- Migration: Add new table with full setup
-- File: supabase/migrations/YYYYMMDDHHMMSS_add_<entity>_table.sql

-- =====================================================
-- Table Definition
-- =====================================================

CREATE TABLE IF NOT EXISTS <entity> (
  -- Primary key
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  
  -- Audit fields
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  deleted_at TIMESTAMPTZ,
  created_by UUID REFERENCES auth.users(id),
  updated_by UUID REFERENCES auth.users(id),
  
  -- Business fields
  name TEXT NOT NULL,
  -- ... other fields
  
  -- Constraints
  CONSTRAINT <entity>_name_not_empty CHECK (name <> '')
);

-- =====================================================
-- Indexes
-- =====================================================

CREATE INDEX idx_<entity>_name ON <entity>(name);
CREATE INDEX idx_<entity>_created_at ON <entity>(created_at DESC);

-- =====================================================
-- Row Level Security (RLS)
-- =====================================================

ALTER TABLE <entity> ENABLE ROW LEVEL SECURITY;

CREATE POLICY authenticated_select_<entity> ON <entity>
  FOR SELECT TO authenticated
  USING (deleted_at IS NULL);

-- =====================================================
-- Triggers
-- =====================================================

-- Auto-update updated_at timestamp
CREATE TRIGGER update_<entity>_updated_at
  BEFORE UPDATE ON <entity>
  FOR EACH ROW
  EXECUTE FUNCTION update_updated_at_column();
```

## Resource Files

### Core Patterns
- [Service Layer Architecture](resources/service-layer.md) - DataProvider abstraction and business logic
- [Validation Patterns](resources/validation-patterns.md) - Zod schemas and CRM-specific validation
- [Error Handling](resources/error-handling.md) - Custom error hierarchy and Supabase error mapping

### Supabase-Specific
- [RLS Policies](resources/rls-policies.md) - Row Level Security patterns for shared team access
- [Edge Functions](resources/edge-functions.md) - Deno.serve patterns and service role operations
- [Query Optimization](resources/query-optimization.md) - Indexing, pagination, and view optimization

### CRM Business Logic
- [Organizations](resources/organizations.md) - Hierarchy validation, rollup metrics, and deletion protection

### Future Resources (Coming Soon)
- Contacts - Multi-organization relationships and JSONB arrays
- Opportunities - Stage workflows and product management
- Activities - Interaction tracking and reporting
- Migrations - Schema evolution and best practices
- Real-time Subscriptions - Live data updates
- Storage Patterns - File uploads and management
- Testing - Service layer and database testing

## Key Conventions

### File Naming
- Service classes: `<entity>.service.ts` (e.g., `organizations.service.ts`)
- Edge Functions: `supabase/functions/<function-name>/index.ts`
- Migrations: `supabase/migrations/YYYYMMDDHHMMSS_<description>.sql`
- RPC schemas: `src/validation/rpc.ts`

### TypeScript Patterns
```typescript
// Always use DataProvider, never direct Supabase client
// ✅ GOOD
const { data } = await this.dataProvider.create('organizations', { data });

// ❌ BAD - bypasses single source of truth
const { data } = await supabase.from('organizations').insert(data);
```

### SQL Patterns
```sql
-- Always include audit fields
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,

-- Always use soft delete (deleted_at IS NULL in queries)
-- Never use WHERE deleted_at IS NULL in policies (performance)
```

### Validation Patterns
```typescript
// Define schema once, infer types
const CreateOrgSchema = z.object({
  name: z.string().min(1),
  org_type: z.enum(['restaurant', 'distributor', 'supplier'])
});

type CreateOrgInput = z.infer<typeof CreateOrgSchema>;

// Use in service
const validated = CreateOrgSchema.parse(input);
```

## Common Operations

### Creating a New Service

```typescript
import type { DataProvider } from 'ra-core';
import { z } from 'zod';

// 1. Define validation schemas
const CreateSchema = z.object({
  // fields
});

const UpdateSchema = CreateSchema.partial();

// 2. Define types from schemas
type CreateInput = z.infer<typeof CreateSchema>;
type UpdateInput = z.infer<typeof UpdateSchema>;

// 3. Create service class
export class EntityService {
  constructor(private dataProvider: DataProvider) {}

  async create(input: CreateInput): Promise<Entity> {
    const validated = CreateSchema.parse(input);
    // Business logic
    const { data } = await this.dataProvider.create('entities', { data: validated });
    return data;
  }

  async update(id: string, input: UpdateInput): Promise<Entity> {
    const validated = UpdateSchema.parse(input);
    // Business logic
    const { data } = await this.dataProvider.update('entities', { id, data: validated });
    return data;
  }
}
```

### Creating an Edge Function

```bash
# 1. Create function directory
supabase functions new my-function

# 2. Implement in supabase/functions/my-function/index.ts
# (See Edge Functions resource for template)

# 3. Deploy
supabase functions deploy my-function
```

### Creating a Migration

```bash
# 1. Create migration file
supabase migration new add_entity_table

# 2. Write SQL in generated file
# (See Migrations resource for template)

# 3. Apply migration
supabase db push
```

## Performance Considerations

### Indexes
- Always index foreign keys
- Index columns used in WHERE clauses
- Use composite indexes for multi-column queries
- Index DESC for ORDER BY DESC queries

### RLS Performance
- Avoid `deleted_at IS NULL` in policies (use in queries instead)
- Use indexes on columns referenced in policies
- Keep policies simple (complex logic in Edge Functions)

### Query Optimization
- Use `.select()` to specify only needed columns
- Batch operations when possible
- Use `.maybeSingle()` instead of `.single()` for optional queries
- Leverage Postgres EXPLAIN ANALYZE for slow queries

## Troubleshooting

### RLS Policy Not Working
1. Check if RLS is enabled: `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`
2. Verify policy applies to correct role (`TO authenticated`)
3. Test policy in SQL editor with: `SET ROLE authenticated; SELECT ...`
4. Check for missing indexes on policy columns

### Edge Function Failing
1. Check Deno logs: `supabase functions logs my-function`
2. Verify environment variables are set
3. Test locally: `supabase functions serve my-function`
4. Check CORS headers if calling from browser

### Service Layer Error
1. Verify DataProvider is initialized correctly
2. Check Zod validation errors
3. Review error logs from console.error statements
4. Ensure database schema matches TypeScript types

## Related Skills

- **crispy-design-system** - UI components that consume service layer
- **skill-developer** - Creating new skills for specific features

## Version

- Skill Version: 1.0
- Last Updated: 2025-11-13
- Crispy-CRM Target: MVP (Phase 1-3)