@@ -121,12 +121,16 @@ type HistoryMessage struct {
121121}
122122
123123func (s * server ) saveMessageToHistory (userID , chatJID , senderJID , messageID , messageType , textContent , mediaLink , quotedMessageID , dataJson string ) error {
124- query := `INSERT INTO message_history (user_id, chat_jid, sender_jid, message_id, timestamp, message_type, text_content, media_link, quoted_message_id, datajson)
125- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
126- if s .db .DriverName () == "sqlite" {
127- query = `INSERT INTO message_history (user_id, chat_jid, sender_jid, message_id, timestamp, message_type, text_content, media_link, quoted_message_id, datajson)
128- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
129- }
124+ // Idempotent insert: HistorySync batches (and reconnects) routinely redeliver
125+ // messages already persisted via the live Message event. The (user_id, message_id)
126+ // unique constraint makes those duplicates an expected condition, not an error,
127+ // so skip them silently instead of failing the insert and logging at ERROR. See #292.
128+ // Rebind adapts the ? placeholders to the active driver ($1.. on Postgres,
129+ // ? on SQLite), so the query is defined once. ON CONFLICT DO NOTHING is valid
130+ // on both Postgres and modern SQLite.
131+ query := s .db .Rebind (`INSERT INTO message_history (user_id, chat_jid, sender_jid, message_id, timestamp, message_type, text_content, media_link, quoted_message_id, datajson)
132+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
133+ ON CONFLICT (user_id, message_id) DO NOTHING` )
130134 _ , err := s .db .Exec (query , userID , chatJID , senderJID , messageID , time .Now (), messageType , textContent , mediaLink , quotedMessageID , dataJson )
131135 if err != nil {
132136 return fmt .Errorf ("failed to save message to history: %w" , err )
0 commit comments