// Reply, forward attribution, and (multiple) pinned messages. /** @param {import('node-pg-migrate').MigrationBuilder} pgm */ exports.up = (pgm) => { pgm.addColumns('messages', { // Reply target; if the replied message is hard-deleted the link clears. reply_to_id: { type: 'uuid', references: 'messages', onDelete: 'SET NULL' }, // Whether this message is a forward, and a snapshot of the original author // (PublicUser jsonb) — null when the forwarder chose to hide the origin. forwarded: { type: 'boolean', notNull: true, default: false }, forwarded_from: { type: 'jsonb' }, }); pgm.createIndex('messages', 'reply_to_id'); // Pinned messages (multiple per conversation), newest pin first. pgm.createTable('message_pins', { conversation_id: { type: 'uuid', notNull: true, references: 'conversations', onDelete: 'CASCADE', }, message_id: { type: 'uuid', notNull: true, references: 'messages', onDelete: 'CASCADE' }, pinned_by: { type: 'uuid', notNull: true, references: 'users', onDelete: 'CASCADE' }, pinned_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, }); pgm.addConstraint('message_pins', 'message_pins_pkey', { primaryKey: ['conversation_id', 'message_id'], }); pgm.createIndex('message_pins', ['conversation_id', 'pinned_at']); }; /** @param {import('node-pg-migrate').MigrationBuilder} pgm */ exports.down = (pgm) => { pgm.dropTable('message_pins'); pgm.dropColumns('messages', ['reply_to_id', 'forwarded', 'forwarded_from']); };