52 lines
2.3 KiB
JavaScript
52 lines
2.3 KiB
JavaScript
// Phase 3 — rooms, membership, and messages.
|
|
// Messages use a crypto-agnostic content envelope so E2EE can be layered on
|
|
// later with no schema change (content holds plaintext now, ciphertext later).
|
|
|
|
/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
|
|
exports.up = (pgm) => {
|
|
pgm.createTable('rooms', {
|
|
id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') },
|
|
name: { type: 'text', notNull: true },
|
|
created_by: { type: 'uuid', notNull: true, references: 'users', onDelete: 'RESTRICT' },
|
|
created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') },
|
|
});
|
|
|
|
pgm.createTable('room_members', {
|
|
room_id: { type: 'uuid', notNull: true, references: 'rooms', onDelete: 'CASCADE' },
|
|
user_id: { type: 'uuid', notNull: true, references: 'users', onDelete: 'CASCADE' },
|
|
role: { type: 'text', notNull: true, default: 'member' },
|
|
joined_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') },
|
|
});
|
|
pgm.addConstraint('room_members', 'room_members_pkey', {
|
|
primaryKey: ['room_id', 'user_id'],
|
|
});
|
|
pgm.createIndex('room_members', 'user_id');
|
|
|
|
pgm.createTable('messages', {
|
|
id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') },
|
|
// Server-assigned monotonic ordering.
|
|
seq: { type: 'bigserial', notNull: true },
|
|
room_id: { type: 'uuid', notNull: true, references: 'rooms', onDelete: 'CASCADE' },
|
|
sender_id: { type: 'uuid', notNull: true, references: 'users', onDelete: 'RESTRICT' },
|
|
// Client-generated idempotency / dedupe key.
|
|
client_msg_id: { type: 'text', notNull: true },
|
|
// Opaque content envelope (see file header).
|
|
content: { type: 'text', notNull: true },
|
|
content_type: { type: 'text', notNull: true, default: 'text' },
|
|
encryption: { type: 'text' },
|
|
created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') },
|
|
});
|
|
pgm.createIndex('messages', 'seq', { unique: true });
|
|
pgm.createIndex('messages', ['room_id', 'seq']);
|
|
// Idempotent sends: one message per (room, sender, client_msg_id).
|
|
pgm.addConstraint('messages', 'messages_dedupe_uq', {
|
|
unique: ['room_id', 'sender_id', 'client_msg_id'],
|
|
});
|
|
};
|
|
|
|
/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
|
|
exports.down = (pgm) => {
|
|
pgm.dropTable('messages');
|
|
pgm.dropTable('room_members');
|
|
pgm.dropTable('rooms');
|
|
};
|