Skip to main content

Users Data Model

Entity Relationship

Schema

The users table stores all registered AskAtlas users, synced from Clerk via webhooks.

CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
clerk_id VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
middle_name VARCHAR(255),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}'::jsonb
);

Key Columns

ColumnPurpose
idInternal UUID, used as the primary key across all tables
clerk_idExternal Clerk user ID, used for webhook lookups and auth middleware resolution
emailUnique email, synced from Clerk
deleted_atSoft-delete timestamp — NULL means active
metadataFlexible JSONB field for future extensibility

Soft Delete Pattern

Users are never hard-deleted. When Clerk sends a user.deleted event, we set deleted_at = NOW(). This preserves referential integrity — files, grants, and other records still reference the user.

The UpsertClerkUser query also clears deleted_at back to NULL, allowing a user to be "undeleted" if Clerk recreates them.

Indexes

IndexPurpose
idx_users_deleted_atPartial index on deleted_at IS NULL — fast lookups of active users
idx_users_active_emailPartial index on email WHERE deleted_at IS NULL — email lookups for active users only

SQL Queries

QueryPurpose
UpsertClerkUserInsert or update user from Clerk webhook data (idempotent)
SoftDeleteUserByClerkIDSet deleted_at = NOW() for a user by Clerk ID
GetUserIDByClerkIDResolve Clerk ID to internal UUID (used by auth middleware)