-- PDS database schema (initial) CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ===================================================== -- users -- ===================================================== CREATE TABLE users ( did TEXT PRIMARY KEY, -- did:plc:... handle TEXT NOT NULL UNIQUE, -- alice.maarcadetweet.local email TEXT, -- nullable for did:web password_hash TEXT, -- argon2id; nullable signing_key BYTEA NOT NULL, -- compressed secp256k1 pubkey rotation_key BYTEA NOT NULL, -- for PLC rotation ops created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX users_handle_idx ON users (LOWER(handle)); -- ===================================================== -- repos -- ===================================================== CREATE TABLE repos ( did TEXT PRIMARY KEY REFERENCES users(did) ON DELETE CASCADE, rev TEXT NOT NULL, -- TID-encoded revision counter head_cid BYTEA NOT NULL, -- CID of latest commit head_commit BYTEA NOT NULL, -- CBOR block of latest commit prev_commit BYTEA, -- for fast linear back-link indexed_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ===================================================== -- repo blocks (MST nodes + records) -- ===================================================== CREATE TABLE repo_blocks ( did TEXT NOT NULL, cid BYTEA NOT NULL, block BYTEA NOT NULL, size INTEGER NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (did, cid) ); CREATE INDEX repo_blocks_did_idx ON repo_blocks (did); -- ===================================================== -- blobs -- ===================================================== CREATE TABLE blobs ( cid TEXT PRIMARY KEY, -- bafy... did TEXT NOT NULL, mime_type TEXT NOT NULL, size BIGINT NOT NULL, storage_key TEXT NOT NULL, -- S3 object key created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX blobs_did_idx ON blobs (did); -- ===================================================== -- sessions -- ===================================================== CREATE TABLE sessions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), did TEXT NOT NULL REFERENCES users(did) ON DELETE CASCADE, access_jwt TEXT NOT NULL, -- ES256K signed refresh_jwt TEXT NOT NULL, -- ES256 signed (different key) access_expires_at TIMESTAMPTZ NOT NULL, refresh_expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), revoked_at TIMESTAMPTZ ); CREATE INDEX sessions_did_idx ON sessions (did); -- ===================================================== -- plc operations (audit log of submitted ops) -- ===================================================== CREATE TABLE plc_ops ( id BIGSERIAL PRIMARY KEY, did TEXT NOT NULL, prev TEXT, -- CID of previous op, or null for create op_cid TEXT NOT NULL, signed_op BYTEA NOT NULL, -- DAG-CBOR submitted BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX plc_ops_did_idx ON plc_ops (did); -- ===================================================== -- update trigger for users.updated_at -- ===================================================== CREATE OR REPLACE FUNCTION touch_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER users_touch BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION touch_updated_at();