CREATE EXTENSION IF NOT EXISTS "pgcrypto"; CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username TEXT UNIQUE NOT NULL, client_salt TEXT NOT NULL, encrypted_master_key TEXT NOT NULL, hashed_auth_key TEXT NOT NULL, public_identity_key TEXT NOT NULL, public_signing_key TEXT NOT NULL, encrypted_private_keys TEXT NOT NULL, is_admin BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS channels ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT UNIQUE NOT NULL, type TEXT DEFAULT 'text' CHECK (type IN ('text', 'voice')), created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS roles ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, color TEXT DEFAULT '#99aab5', position INTEGER DEFAULT 0, permissions JSONB DEFAULT '{}', is_hoist BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS user_roles ( user_id UUID REFERENCES users(id) ON DELETE CASCADE, role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, PRIMARY KEY (user_id, role_id) ); CREATE TABLE IF NOT EXISTS channel_keys ( channel_id UUID NOT NULL, user_id UUID NOT NULL, encrypted_key_bundle TEXT NOT NULL, key_version INTEGER DEFAULT 1, PRIMARY KEY (channel_id, user_id) ); CREATE TABLE IF NOT EXISTS messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel_id UUID NOT NULL, sender_id UUID NOT NULL, ciphertext TEXT NOT NULL, nonce TEXT NOT NULL, signature TEXT NOT NULL, key_version INTEGER NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS invites ( code TEXT PRIMARY KEY, -- e.g. "8f1a4c..." (The ID of the invite) encrypted_payload TEXT NOT NULL, -- The AES-Encrypted Key Bundle (Server can't read this) created_by UUID REFERENCES users(id) ON DELETE CASCADE, max_uses INTEGER DEFAULT NULL, -- NULL = Infinite uses INTEGER DEFAULT 0, expires_at TIMESTAMPTZ DEFAULT NULL, -- NULL = Never key_version INTEGER NOT NULL, -- Which key version is inside? (So we can invalidate leaks) created_at TIMESTAMPTZ DEFAULT NOW() );