Skip to main content

Database Schema

Postgres 17 via Drizzle ORM (src/db/schema.ts). All timestamps are timestamptz. Migrations live in drizzle/ and are applied with npm run db:migrate.

user_profiles ──1:*──> sessions (ON DELETE cascade)
user_profiles ──1:*──> conversations
conversations ──1:*──> messages

user_profiles​

ColumnTypeNotes
iduuid PKdefault random
keycloak_subtext, uniquenullable (local admin has none)
auth_providertext NOT NULL'keycloak' | 'local'
usernametext NOT NULL
emailtext
display_nametext
password_hashtextlocal users only (argon2id)
is_adminbool NOT NULLdefault false
is_service_accountbool NOT NULLdefault false
created_at / updated_attimestamptz NOT NULLdefault now()

Partial unique index user_profiles_local_username_idx on username WHERE auth_provider='local' — guards the admin-bootstrap race while leaving Keycloak usernames unconstrained.

sessions​

ColumnTypeNotes
idtext PK32-byte base64url random
user_iduuid NOT NULL → user_profiles.idON DELETE cascade
kc_tokensjsonbnullable; {enc: <AES-GCM base64>} or null for local sessions
expires_attimestamptz NOT NULLsliding 7-day idle TTL
created_attimestamptz NOT NULLdefault now()

conversations​

ColumnTypeNotes
iduuid PKclient-supplied (no default)
user_iduuid NOT NULL → user_profiles.id
titletextfirst 80 chars of the first message
modeltext NOT NULLINFERENCE_MODEL at creation
deleted_attimestamptznullable — soft delete
created_at / updated_attimestamptz NOT NULLdefault now()

Index conversations_user_updated_idx on (user_id, updated_at).

messages​

ColumnTypeNotes
iduuid PKdefault gen_random_uuid()
conversation_iduuid NOT NULL → conversations.id
roletext NOT NULL'system' | 'user' | 'assistant'
contenttext NOT NULL
prompt_tokensintegernullable
completion_tokensintegernullable
interruptedbool NOT NULLdefault false
created_attimestamptz NOT NULLdefault now()

Index messages_conversation_created_idx on (conversation_id, created_at).

Migrations​

FileContents
drizzle/0000_warm_may_parker.sqltables, FKs, messages index
drizzle/0001_busy_fallen_one.sqlconversations composite index + partial unique local-username index

Author schema changes in src/db/schema.ts, then:

npm run db:generate # drizzle-kit emits SQL into drizzle/
npm run db:migrate # scripts/migrate.mjs applies them