Skip to main content

circles

Overview

Circles are small, named groups that users create to organize friends around shared contexts (e.g., "Book Club", "Work Friends"). Each circle has a creator (created_by), an optional description, and name/description length constraints. Circle membership is invitation-based and admin-controlled. Circles serve as audience scopes for posts.

Relevant Invariants

  • Invariant #7: "Human-Scale Social Contexts" -- Circles enforce small-group contexts
  • Invariant #1: "Participation Is Always Voluntary" -- Circle membership is invitation-based

Schema

-- From 20260131_circles_schema.sql + 20260208_create_circle_rpc.sql
CREATE TABLE circles (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL CHECK (char_length(name) <= 50),
description TEXT DEFAULT NULL CHECK (char_length(description) <= 200),
created_by UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
circle_type TEXT DEFAULT 'static' CHECK (circle_type IN ('static', 'dynamic')),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);

Columns

ColumnTypeNullableDefaultDescription
iduuidNogen_random_uuid()Primary key
nametextNo--Circle display name (max 50 characters)
descriptiontextYesNULLOptional circle description (max 200 characters)
created_byuuidNo--Circle creator, references profiles(id)
circle_typetextYes'static'Circle type: 'static' (manual membership) or 'dynamic' (rule-based)
created_attimestamptzNoNOW()Creation timestamp
updated_attimestamptzNoNOW()Last update timestamp (auto-updated by trigger)

Constraints

  • CHECK (char_length(name) <= 50) -- Name limited to 50 characters
  • CHECK (char_length(description) <= 200) -- Description limited to 200 characters
  • CHECK (circle_type IN ('static', 'dynamic')) -- Valid circle types

RPC Functions

-- Creates a circle and adds creator as admin member atomically
-- SECURITY DEFINER to bypass the chicken-and-egg RLS problem
-- (circle_members INSERT requires admin, but creator isn't a member yet)
create_circle(p_name TEXT, p_description TEXT DEFAULT NULL) RETURNS UUID

Triggers

-- Auto-updates updated_at on row changes
CREATE TRIGGER circles_updated
BEFORE UPDATE ON circles
FOR EACH ROW EXECUTE FUNCTION update_circles_updated_at();

Helper Functions

The circles schema defines three SECURITY DEFINER helper functions used by RLS policies:

-- Check if user is a circle member (status = 'member')
is_circle_member(circle_uuid UUID, user_uuid UUID) RETURNS BOOLEAN

-- Check if user is a circle admin (role = 'admin' AND status = 'member')
is_circle_admin(circle_uuid UUID, user_uuid UUID) RETURNS BOOLEAN

-- Check if user has any access (member or invited)
has_circle_access(circle_uuid UUID, user_uuid UUID) RETURNS BOOLEAN

RLS Policies

-- SELECT: Members and invitees can view their circles
CREATE POLICY "Circle members can view circle"
ON circles FOR SELECT TO authenticated
USING (has_circle_access(id, auth.uid()));

-- INSERT: Authenticated users can create circles (must set created_by to self)
CREATE POLICY "Users can create circles"
ON circles FOR INSERT TO authenticated
WITH CHECK (auth.uid() = created_by);

-- UPDATE: Only circle admins can update
CREATE POLICY "Circle admins can update"
ON circles FOR UPDATE TO authenticated
USING (is_circle_admin(id, auth.uid()));

-- DELETE: Only circle admins can delete
CREATE POLICY "Circle admins can delete"
ON circles FOR DELETE TO authenticated
USING (is_circle_admin(id, auth.uid()));

Last updated: 2026-02-07