1+ -- Enable UUID generator
12CREATE EXTENSION IF NOT EXISTS " pgcrypto" ;
23
4+ -- =========================
5+ -- Users
6+ -- =========================
37CREATE TABLE users (
48 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
59 username TEXT UNIQUE NOT NULL ,
610 created_at TIMESTAMP DEFAULT NOW()
711);
812
13+ -- =========================
14+ -- Devices
15+ -- =========================
916CREATE TABLE devices (
1017 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1118 user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE ,
12- identity_pubkey TEXT NOT NULL ,
19+ identity_pubkey TEXT NOT NULL ,
1320 prekey_pubkey TEXT NOT NULL ,
1421 signed_prekey_pub TEXT NOT NULL ,
1522 signed_prekey_sig TEXT NOT NULL ,
16- one_time_prekeys JSONB NOT NULL ,
23+ one_time_prekeys JSONB NOT NULL , -- array of one-time public prekeys
1724 device_label TEXT ,
1825 push_token TEXT ,
1926 last_seen TIMESTAMP DEFAULT NOW(),
2027 created_at TIMESTAMP DEFAULT NOW(),
21- UNIQUE(user_id, identity_pubkey)
28+ UNIQUE (user_id, identity_pubkey)
2229);
2330
31+ -- =========================
32+ -- Chats
33+ -- =========================
2434CREATE TABLE chats (
25- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
26- user_a_id UUID REFERENCES users(id) ON DELETE CASCADE ,
27- user_b_id UUID REFERENCES users(id) ON DELETE CASCADE ,
28- created_at TIMESTAMP DEFAULT NOW(),
29- UNIQUE(user_a_id, user_b_id)
35+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
36+ type TEXT CHECK (type IN (' direct' ,' group' )) DEFAULT ' direct' ,
37+
38+ -- Direct chat participants
39+ user_a UUID REFERENCES users(id) ON DELETE CASCADE ,
40+ user_b UUID REFERENCES users(id) ON DELETE CASCADE ,
41+
42+ -- Group chat info
43+ name TEXT ,
44+ owner_id UUID REFERENCES users(id) ON DELETE SET NULL ,
45+
46+ -- Will be linked after messages table is created
47+ last_message_id UUID,
48+
49+ created_at TIMESTAMP DEFAULT NOW(),
50+ updated_at TIMESTAMP DEFAULT NOW(),
51+
52+ -- For direct chats: both users must be set, and enforce strict ordering
53+ CONSTRAINT chats_direct_shape
54+ CHECK (
55+ type <> ' direct'
56+ OR (user_a IS NOT NULL AND user_b IS NOT NULL AND user_a < user_b)
57+ )
3058);
3159
60+ -- Prevent duplicates (A,B) vs (B,A)
61+ CREATE UNIQUE INDEX uniq_direct_chat
62+ ON chats (LEAST(user_a, user_b), GREATEST(user_a, user_b))
63+ WHERE type = ' direct' ;
3264
33- CREATE TABLE messages (
34- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
65+ -- =========================
66+ -- Chat Members (for group chats)
67+ -- =========================
68+ CREATE TABLE chat_members (
3569 chat_id UUID REFERENCES chats(id) ON DELETE CASCADE ,
36- from_device_id UUID REFERENCES devices(id) ON DELETE SET NULL ,
37- plaintext_hash TEXT , -- SHA256 du plaintext avant chiffrement
38- created_at TIMESTAMP DEFAULT NOW()
70+ user_id UUID REFERENCES users(id) ON DELETE CASCADE ,
71+ role TEXT DEFAULT ' member' ,
72+ joined_at TIMESTAMP DEFAULT NOW(),
73+ PRIMARY KEY (chat_id, user_id)
3974);
4075
41- CREATE TABLE message_deliveries (
76+ -- =========================
77+ -- Messages
78+ -- =========================
79+ CREATE TABLE messages (
4280 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
43- message_id UUID REFERENCES messages(id) ON DELETE CASCADE ,
81+
82+ -- Logical message id (shared by all device fanouts)
83+ logical_msg_id TEXT NOT NULL ,
84+
85+ chat_id UUID REFERENCES chats(id) ON DELETE CASCADE ,
86+
87+ from_user_id UUID REFERENCES users(id) ON DELETE CASCADE ,
88+ from_device_id UUID REFERENCES devices(id) ON DELETE CASCADE ,
89+
90+ to_user_id UUID REFERENCES users(id) ON DELETE CASCADE ,
4491 to_device_id UUID REFERENCES devices(id) ON DELETE CASCADE ,
45- ciphertext TEXT NOT NULL ,
46- delivered BOOLEAN DEFAULT FALSE,
92+
93+ header JSONB NOT NULL , -- Double Ratchet header (DH pubkey, counters, etc.)
94+ ciphertext TEXT NOT NULL , -- base64(nonce || cipher || mac)
95+
96+ delivered_at TIMESTAMP ,
97+ read_at TIMESTAMP ,
4798 created_at TIMESTAMP DEFAULT NOW()
4899);
49100
101+ -- Add FK once messages table exists
102+ ALTER TABLE chats
103+ ADD CONSTRAINT fk_chats_last_message
104+ FOREIGN KEY (last_message_id)
105+ REFERENCES messages(id)
106+ ON DELETE SET NULL ;
107+
108+ -- Indexes
109+ CREATE INDEX idx_messages_todevice ON messages(to_device_id, created_at);
110+ CREATE INDEX idx_messages_chatid ON messages(chat_id, created_at);
111+ CREATE INDEX idx_messages_logical ON messages(logical_msg_id);
50112
113+ -- =========================
114+ -- Sessions (metadata only, no secret keys)
115+ -- =========================
51116CREATE TABLE sessions (
52117 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
53118 chat_id UUID REFERENCES chats(id) ON DELETE CASCADE ,
54119 sender_device_id UUID REFERENCES devices(id) ON DELETE CASCADE ,
55120 receiver_device_id UUID REFERENCES devices(id) ON DELETE CASCADE ,
56- root_key BYTEA NOT NULL ,
57- send_chain_key BYTEA ,
58- recv_chain_key BYTEA ,
59- send_counter INTEGER DEFAULT 0 ,
60- recv_counter INTEGER DEFAULT 0 ,
61- ratchet_pub BYTEA ,
62- last_remote_pub BYTEA ,
63121 created_at TIMESTAMP DEFAULT NOW(),
64122 updated_at TIMESTAMP DEFAULT NOW(),
65- UNIQUE(sender_device_id, receiver_device_id)
123+ UNIQUE (sender_device_id, receiver_device_id)
66124);
67125
126+ -- =========================
127+ -- Used Tokens (anti-replay or enrollment)
128+ -- =========================
68129CREATE TABLE used_tokens (
69130 token TEXT PRIMARY KEY ,
70131 used_at TIMESTAMP DEFAULT NOW()
71132);
72133
73-
134+ -- =========================
135+ -- Pending Sessions (X3DH handshake initialization)
136+ -- =========================
74137CREATE TABLE pending_sessions (
75138 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
76- sender_device_id UUID NOT NULL REFERENCES devices(id),
77- recipient_device_id UUID NOT NULL REFERENCES devices(id),
78- ephemeral_pubkey TEXT NOT NULL ,
79- sender_prekey_pub TEXT NOT NULL ,
80- otpk_used TEXT NOT NULL ,
81- ciphertext TEXT NOT NULL ,
139+ sender_device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE ,
140+ recipient_device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE ,
141+ ephemeral_pubkey TEXT NOT NULL , -- Ephemeral key (EK_A pub)
142+ sender_prekey_pub TEXT NOT NULL , -- IK/SPK_A used by sender
143+ otpk_used TEXT NOT NULL , -- Whether a one-time prekey was consumed
144+ ciphertext TEXT NOT NULL , -- Encrypted payload (init message)
82145 created_at TIMESTAMP DEFAULT NOW(),
83- state TEXT DEFAULT ' initiated' CHECK (state IN (' initiated' , ' responded' , ' completed' ))
146+ state TEXT DEFAULT ' initiated' CHECK (state IN (' initiated' ,' responded' ,' completed' ))
84147);
85148
86- ALTER TABLE chats
87- ADD CONSTRAINT chats_unique_pair CHECK (user_a_id < user_b_id);
88-
149+ CREATE INDEX idx_pending_sessions_recipient ON pending_sessions(recipient_device_id, created_at);
89150
151+ -- =========================
152+ -- Convenience View: Public device list
153+ -- =========================
90154CREATE VIEW user_devices_view AS
91- SELECT
155+ SELECT
92156 u .username ,
93157 d .id AS device_id,
94158 d .identity_pubkey ,
95159 d .signed_prekey_pub ,
96160 d .signed_prekey_sig ,
97- (d .one_time_prekeys - > 0 ) AS one_time_prekey_pub
161+ (d .one_time_prekeys - >> 0 ) AS one_time_prekey_pub
98162FROM users u
99- JOIN devices d ON u .id = d .user_id ;
163+ JOIN devices d ON u .id = d .user_id ;
0 commit comments