messages.rs

  1use super::*;
  2use rpc::Notification;
  3use sea_orm::{SelectColumns, TryInsertResult};
  4use time::OffsetDateTime;
  5use util::ResultExt;
  6
  7impl Database {
  8    /// Inserts a record representing a user joining the chat for a given channel.
  9    pub async fn join_channel_chat(
 10        &self,
 11        channel_id: ChannelId,
 12        connection_id: ConnectionId,
 13        user_id: UserId,
 14    ) -> Result<()> {
 15        self.transaction(|tx| async move {
 16            let channel = self.get_channel_internal(channel_id, &tx).await?;
 17            self.check_user_is_channel_participant(&channel, user_id, &tx)
 18                .await?;
 19            channel_chat_participant::ActiveModel {
 20                id: ActiveValue::NotSet,
 21                channel_id: ActiveValue::Set(channel_id),
 22                user_id: ActiveValue::Set(user_id),
 23                connection_id: ActiveValue::Set(connection_id.id as i32),
 24                connection_server_id: ActiveValue::Set(ServerId(connection_id.owner_id as i32)),
 25            }
 26            .insert(&*tx)
 27            .await?;
 28            Ok(())
 29        })
 30        .await
 31    }
 32
 33    /// Removes `channel_chat_participant` records associated with the given connection ID.
 34    pub async fn channel_chat_connection_lost(
 35        &self,
 36        connection_id: ConnectionId,
 37        tx: &DatabaseTransaction,
 38    ) -> Result<()> {
 39        channel_chat_participant::Entity::delete_many()
 40            .filter(
 41                Condition::all()
 42                    .add(
 43                        channel_chat_participant::Column::ConnectionServerId
 44                            .eq(connection_id.owner_id),
 45                    )
 46                    .add(channel_chat_participant::Column::ConnectionId.eq(connection_id.id)),
 47            )
 48            .exec(tx)
 49            .await?;
 50        Ok(())
 51    }
 52
 53    /// Removes `channel_chat_participant` records associated with the given user ID so they
 54    /// will no longer get chat notifications.
 55    pub async fn leave_channel_chat(
 56        &self,
 57        channel_id: ChannelId,
 58        connection_id: ConnectionId,
 59        _user_id: UserId,
 60    ) -> Result<()> {
 61        self.transaction(|tx| async move {
 62            channel_chat_participant::Entity::delete_many()
 63                .filter(
 64                    Condition::all()
 65                        .add(
 66                            channel_chat_participant::Column::ConnectionServerId
 67                                .eq(connection_id.owner_id),
 68                        )
 69                        .add(channel_chat_participant::Column::ConnectionId.eq(connection_id.id))
 70                        .add(channel_chat_participant::Column::ChannelId.eq(channel_id)),
 71                )
 72                .exec(&*tx)
 73                .await?;
 74
 75            Ok(())
 76        })
 77        .await
 78    }
 79
 80    /// Retrieves the messages in the specified channel.
 81    ///
 82    /// Use `before_message_id` to paginate through the channel's messages.
 83    pub async fn get_channel_messages(
 84        &self,
 85        channel_id: ChannelId,
 86        user_id: UserId,
 87        count: usize,
 88        before_message_id: Option<MessageId>,
 89    ) -> Result<Vec<proto::ChannelMessage>> {
 90        self.transaction(|tx| async move {
 91            let channel = self.get_channel_internal(channel_id, &tx).await?;
 92            self.check_user_is_channel_participant(&channel, user_id, &tx)
 93                .await?;
 94
 95            let mut condition =
 96                Condition::all().add(channel_message::Column::ChannelId.eq(channel_id));
 97
 98            if let Some(before_message_id) = before_message_id {
 99                condition = condition.add(channel_message::Column::Id.lt(before_message_id));
100            }
101
102            let rows = channel_message::Entity::find()
103                .filter(condition)
104                .order_by_desc(channel_message::Column::Id)
105                .limit(count as u64)
106                .all(&*tx)
107                .await?;
108
109            self.load_channel_messages(rows, &tx).await
110        })
111        .await
112    }
113
114    /// Returns the channel messages with the given IDs.
115    pub async fn get_channel_messages_by_id(
116        &self,
117        user_id: UserId,
118        message_ids: &[MessageId],
119    ) -> Result<Vec<proto::ChannelMessage>> {
120        self.transaction(|tx| async move {
121            let rows = channel_message::Entity::find()
122                .filter(channel_message::Column::Id.is_in(message_ids.iter().copied()))
123                .order_by_desc(channel_message::Column::Id)
124                .all(&*tx)
125                .await?;
126
127            let mut channels = HashMap::<ChannelId, channel::Model>::default();
128            for row in &rows {
129                channels.insert(
130                    row.channel_id,
131                    self.get_channel_internal(row.channel_id, &tx).await?,
132                );
133            }
134
135            for (_, channel) in channels {
136                self.check_user_is_channel_participant(&channel, user_id, &tx)
137                    .await?;
138            }
139
140            let messages = self.load_channel_messages(rows, &tx).await?;
141            Ok(messages)
142        })
143        .await
144    }
145
146    async fn load_channel_messages(
147        &self,
148        rows: Vec<channel_message::Model>,
149        tx: &DatabaseTransaction,
150    ) -> Result<Vec<proto::ChannelMessage>> {
151        let mut messages = rows
152            .into_iter()
153            .map(|row| {
154                let nonce = row.nonce.as_u64_pair();
155                proto::ChannelMessage {
156                    id: row.id.to_proto(),
157                    sender_id: row.sender_id.to_proto(),
158                    body: row.body,
159                    timestamp: row.sent_at.assume_utc().unix_timestamp() as u64,
160                    mentions: vec![],
161                    nonce: Some(proto::Nonce {
162                        upper_half: nonce.0,
163                        lower_half: nonce.1,
164                    }),
165                    reply_to_message_id: row.reply_to_message_id.map(|id| id.to_proto()),
166                    edited_at: row
167                        .edited_at
168                        .map(|t| t.assume_utc().unix_timestamp() as u64),
169                }
170            })
171            .collect::<Vec<_>>();
172        messages.reverse();
173
174        let mut mentions = channel_message_mention::Entity::find()
175            .filter(channel_message_mention::Column::MessageId.is_in(messages.iter().map(|m| m.id)))
176            .order_by_asc(channel_message_mention::Column::MessageId)
177            .order_by_asc(channel_message_mention::Column::StartOffset)
178            .stream(tx)
179            .await?;
180
181        let mut message_ix = 0;
182        while let Some(mention) = mentions.next().await {
183            let mention = mention?;
184            let message_id = mention.message_id.to_proto();
185            while let Some(message) = messages.get_mut(message_ix) {
186                if message.id < message_id {
187                    message_ix += 1;
188                } else {
189                    if message.id == message_id {
190                        message.mentions.push(proto::ChatMention {
191                            range: Some(proto::Range {
192                                start: mention.start_offset as u64,
193                                end: mention.end_offset as u64,
194                            }),
195                            user_id: mention.user_id.to_proto(),
196                        });
197                    }
198                    break;
199                }
200            }
201        }
202
203        Ok(messages)
204    }
205
206    fn format_mentions_to_entities(
207        &self,
208        message_id: MessageId,
209        body: &str,
210        mentions: &[proto::ChatMention],
211    ) -> Result<Vec<tables::channel_message_mention::ActiveModel>> {
212        Ok(mentions
213            .iter()
214            .filter_map(|mention| {
215                let range = mention.range.as_ref()?;
216                if !body.is_char_boundary(range.start as usize)
217                    || !body.is_char_boundary(range.end as usize)
218                {
219                    return None;
220                }
221                Some(channel_message_mention::ActiveModel {
222                    message_id: ActiveValue::Set(message_id),
223                    start_offset: ActiveValue::Set(range.start as i32),
224                    end_offset: ActiveValue::Set(range.end as i32),
225                    user_id: ActiveValue::Set(UserId::from_proto(mention.user_id)),
226                })
227            })
228            .collect::<Vec<_>>())
229    }
230
231    /// Creates a new channel message.
232    pub async fn create_channel_message(
233        &self,
234        channel_id: ChannelId,
235        user_id: UserId,
236        body: &str,
237        mentions: &[proto::ChatMention],
238        timestamp: OffsetDateTime,
239        nonce: u128,
240        reply_to_message_id: Option<MessageId>,
241    ) -> Result<CreatedChannelMessage> {
242        self.transaction(|tx| async move {
243            let channel = self.get_channel_internal(channel_id, &tx).await?;
244            self.check_user_is_channel_participant(&channel, user_id, &tx)
245                .await?;
246
247            let mut rows = channel_chat_participant::Entity::find()
248                .filter(channel_chat_participant::Column::ChannelId.eq(channel_id))
249                .stream(&*tx)
250                .await?;
251
252            let mut is_participant = false;
253            let mut participant_connection_ids = HashSet::default();
254            let mut participant_user_ids = Vec::new();
255            while let Some(row) = rows.next().await {
256                let row = row?;
257                if row.user_id == user_id {
258                    is_participant = true;
259                }
260                participant_user_ids.push(row.user_id);
261                participant_connection_ids.insert(row.connection());
262            }
263            drop(rows);
264
265            if !is_participant {
266                Err(anyhow!("not a chat participant"))?;
267            }
268
269            let timestamp = timestamp.to_offset(time::UtcOffset::UTC);
270            let timestamp = time::PrimitiveDateTime::new(timestamp.date(), timestamp.time());
271
272            let result = channel_message::Entity::insert(channel_message::ActiveModel {
273                channel_id: ActiveValue::Set(channel_id),
274                sender_id: ActiveValue::Set(user_id),
275                body: ActiveValue::Set(body.to_string()),
276                sent_at: ActiveValue::Set(timestamp),
277                nonce: ActiveValue::Set(Uuid::from_u128(nonce)),
278                id: ActiveValue::NotSet,
279                reply_to_message_id: ActiveValue::Set(reply_to_message_id),
280                edited_at: ActiveValue::NotSet,
281            })
282            .on_conflict(
283                OnConflict::columns([
284                    channel_message::Column::SenderId,
285                    channel_message::Column::Nonce,
286                ])
287                .do_nothing()
288                .to_owned(),
289            )
290            .do_nothing()
291            .exec(&*tx)
292            .await?;
293
294            let message_id;
295            let mut notifications = Vec::new();
296            match result {
297                TryInsertResult::Inserted(result) => {
298                    message_id = result.last_insert_id;
299                    let mentioned_user_ids =
300                        mentions.iter().map(|m| m.user_id).collect::<HashSet<_>>();
301
302                    let mentions = self.format_mentions_to_entities(message_id, body, mentions)?;
303                    if !mentions.is_empty() {
304                        channel_message_mention::Entity::insert_many(mentions)
305                            .exec(&*tx)
306                            .await?;
307                    }
308
309                    for mentioned_user in mentioned_user_ids {
310                        notifications.extend(
311                            self.create_notification(
312                                UserId::from_proto(mentioned_user),
313                                rpc::Notification::ChannelMessageMention {
314                                    message_id: message_id.to_proto(),
315                                    sender_id: user_id.to_proto(),
316                                    channel_id: channel_id.to_proto(),
317                                },
318                                false,
319                                &tx,
320                            )
321                            .await?,
322                        );
323                    }
324
325                    self.observe_channel_message_internal(channel_id, user_id, message_id, &tx)
326                        .await?;
327                }
328                _ => {
329                    message_id = channel_message::Entity::find()
330                        .filter(channel_message::Column::Nonce.eq(Uuid::from_u128(nonce)))
331                        .one(&*tx)
332                        .await?
333                        .ok_or_else(|| anyhow!("failed to insert message"))?
334                        .id;
335                }
336            }
337
338            Ok(CreatedChannelMessage {
339                message_id,
340                participant_connection_ids,
341                notifications,
342            })
343        })
344        .await
345    }
346
347    pub async fn observe_channel_message(
348        &self,
349        channel_id: ChannelId,
350        user_id: UserId,
351        message_id: MessageId,
352    ) -> Result<NotificationBatch> {
353        self.transaction(|tx| async move {
354            self.observe_channel_message_internal(channel_id, user_id, message_id, &tx)
355                .await?;
356            let mut batch = NotificationBatch::default();
357            batch.extend(
358                self.mark_notification_as_read(
359                    user_id,
360                    &Notification::ChannelMessageMention {
361                        message_id: message_id.to_proto(),
362                        sender_id: Default::default(),
363                        channel_id: Default::default(),
364                    },
365                    &tx,
366                )
367                .await?,
368            );
369            Ok(batch)
370        })
371        .await
372    }
373
374    async fn observe_channel_message_internal(
375        &self,
376        channel_id: ChannelId,
377        user_id: UserId,
378        message_id: MessageId,
379        tx: &DatabaseTransaction,
380    ) -> Result<()> {
381        observed_channel_messages::Entity::insert(observed_channel_messages::ActiveModel {
382            user_id: ActiveValue::Set(user_id),
383            channel_id: ActiveValue::Set(channel_id),
384            channel_message_id: ActiveValue::Set(message_id),
385        })
386        .on_conflict(
387            OnConflict::columns([
388                observed_channel_messages::Column::ChannelId,
389                observed_channel_messages::Column::UserId,
390            ])
391            .update_column(observed_channel_messages::Column::ChannelMessageId)
392            .action_cond_where(observed_channel_messages::Column::ChannelMessageId.lt(message_id))
393            .to_owned(),
394        )
395        // TODO: Try to upgrade SeaORM so we don't have to do this hack around their bug
396        .exec_without_returning(tx)
397        .await?;
398        Ok(())
399    }
400
401    pub async fn observed_channel_messages(
402        &self,
403        channel_ids: &[ChannelId],
404        user_id: UserId,
405        tx: &DatabaseTransaction,
406    ) -> Result<Vec<proto::ChannelMessageId>> {
407        let rows = observed_channel_messages::Entity::find()
408            .filter(observed_channel_messages::Column::UserId.eq(user_id))
409            .filter(
410                observed_channel_messages::Column::ChannelId
411                    .is_in(channel_ids.iter().map(|id| id.0)),
412            )
413            .all(tx)
414            .await?;
415
416        Ok(rows
417            .into_iter()
418            .map(|message| proto::ChannelMessageId {
419                channel_id: message.channel_id.to_proto(),
420                message_id: message.channel_message_id.to_proto(),
421            })
422            .collect())
423    }
424
425    pub async fn latest_channel_messages(
426        &self,
427        channel_ids: &[ChannelId],
428        tx: &DatabaseTransaction,
429    ) -> Result<Vec<proto::ChannelMessageId>> {
430        let mut values = String::new();
431        for id in channel_ids {
432            if !values.is_empty() {
433                values.push_str(", ");
434            }
435            write!(&mut values, "({})", id).unwrap();
436        }
437
438        if values.is_empty() {
439            return Ok(Vec::default());
440        }
441
442        let sql = format!(
443            r#"
444            SELECT
445                *
446            FROM (
447                SELECT
448                    *,
449                    row_number() OVER (
450                        PARTITION BY channel_id
451                        ORDER BY id DESC
452                    ) as row_number
453                FROM channel_messages
454                WHERE
455                    channel_id in ({values})
456            ) AS messages
457            WHERE
458                row_number = 1
459            "#,
460        );
461
462        let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
463        let mut last_messages = channel_message::Model::find_by_statement(stmt)
464            .stream(tx)
465            .await?;
466
467        let mut results = Vec::new();
468        while let Some(result) = last_messages.next().await {
469            let message = result?;
470            results.push(proto::ChannelMessageId {
471                channel_id: message.channel_id.to_proto(),
472                message_id: message.id.to_proto(),
473            });
474        }
475
476        Ok(results)
477    }
478
479    fn get_notification_kind_id_by_name(&self, notification_kind: &str) -> Option<i32> {
480        self.notification_kinds_by_id
481            .iter()
482            .find(|(_, kind)| **kind == notification_kind)
483            .map(|kind| kind.0.0)
484    }
485
486    /// Removes the channel message with the given ID.
487    pub async fn remove_channel_message(
488        &self,
489        channel_id: ChannelId,
490        message_id: MessageId,
491        user_id: UserId,
492    ) -> Result<(Vec<ConnectionId>, Vec<NotificationId>)> {
493        self.transaction(|tx| async move {
494            let mut rows = channel_chat_participant::Entity::find()
495                .filter(channel_chat_participant::Column::ChannelId.eq(channel_id))
496                .stream(&*tx)
497                .await?;
498
499            let mut is_participant = false;
500            let mut participant_connection_ids = Vec::new();
501            while let Some(row) = rows.next().await {
502                let row = row?;
503                if row.user_id == user_id {
504                    is_participant = true;
505                }
506                participant_connection_ids.push(row.connection());
507            }
508            drop(rows);
509
510            if !is_participant {
511                Err(anyhow!("not a chat participant"))?;
512            }
513
514            let result = channel_message::Entity::delete_by_id(message_id)
515                .filter(channel_message::Column::SenderId.eq(user_id))
516                .exec(&*tx)
517                .await?;
518
519            if result.rows_affected == 0 {
520                let channel = self.get_channel_internal(channel_id, &tx).await?;
521                if self
522                    .check_user_is_channel_admin(&channel, user_id, &tx)
523                    .await
524                    .is_ok()
525                {
526                    let result = channel_message::Entity::delete_by_id(message_id)
527                        .exec(&*tx)
528                        .await?;
529                    if result.rows_affected == 0 {
530                        Err(anyhow!("no such message"))?;
531                    }
532                } else {
533                    Err(anyhow!("operation could not be completed"))?;
534                }
535            }
536
537            let notification_kind_id =
538                self.get_notification_kind_id_by_name("ChannelMessageMention");
539
540            let existing_notifications = notification::Entity::find()
541                .filter(notification::Column::EntityId.eq(message_id))
542                .filter(notification::Column::Kind.eq(notification_kind_id))
543                .select_column(notification::Column::Id)
544                .all(&*tx)
545                .await?;
546
547            let existing_notification_ids = existing_notifications
548                .into_iter()
549                .map(|notification| notification.id)
550                .collect();
551
552            // remove all the mention notifications for this message
553            notification::Entity::delete_many()
554                .filter(notification::Column::EntityId.eq(message_id))
555                .filter(notification::Column::Kind.eq(notification_kind_id))
556                .exec(&*tx)
557                .await?;
558
559            Ok((participant_connection_ids, existing_notification_ids))
560        })
561        .await
562    }
563
564    /// Updates the channel message with the given ID, body and timestamp(edited_at).
565    pub async fn update_channel_message(
566        &self,
567        channel_id: ChannelId,
568        message_id: MessageId,
569        user_id: UserId,
570        body: &str,
571        mentions: &[proto::ChatMention],
572        edited_at: OffsetDateTime,
573    ) -> Result<UpdatedChannelMessage> {
574        self.transaction(|tx| async move {
575            let channel = self.get_channel_internal(channel_id, &tx).await?;
576            self.check_user_is_channel_participant(&channel, user_id, &tx)
577                .await?;
578
579            let mut rows = channel_chat_participant::Entity::find()
580                .filter(channel_chat_participant::Column::ChannelId.eq(channel_id))
581                .stream(&*tx)
582                .await?;
583
584            let mut is_participant = false;
585            let mut participant_connection_ids = Vec::new();
586            let mut participant_user_ids = Vec::new();
587            while let Some(row) = rows.next().await {
588                let row = row?;
589                if row.user_id == user_id {
590                    is_participant = true;
591                }
592                participant_user_ids.push(row.user_id);
593                participant_connection_ids.push(row.connection());
594            }
595            drop(rows);
596
597            if !is_participant {
598                Err(anyhow!("not a chat participant"))?;
599            }
600
601            let channel_message = channel_message::Entity::find_by_id(message_id)
602                .filter(channel_message::Column::SenderId.eq(user_id))
603                .one(&*tx)
604                .await?;
605
606            let Some(channel_message) = channel_message else {
607                Err(anyhow!("Channel message not found"))?
608            };
609
610            let edited_at = edited_at.to_offset(time::UtcOffset::UTC);
611            let edited_at = time::PrimitiveDateTime::new(edited_at.date(), edited_at.time());
612
613            let updated_message = channel_message::ActiveModel {
614                body: ActiveValue::Set(body.to_string()),
615                edited_at: ActiveValue::Set(Some(edited_at)),
616                reply_to_message_id: ActiveValue::Unchanged(channel_message.reply_to_message_id),
617                id: ActiveValue::Unchanged(message_id),
618                channel_id: ActiveValue::Unchanged(channel_id),
619                sender_id: ActiveValue::Unchanged(user_id),
620                sent_at: ActiveValue::Unchanged(channel_message.sent_at),
621                nonce: ActiveValue::Unchanged(channel_message.nonce),
622            };
623
624            let result = channel_message::Entity::update_many()
625                .set(updated_message)
626                .filter(channel_message::Column::Id.eq(message_id))
627                .filter(channel_message::Column::SenderId.eq(user_id))
628                .exec(&*tx)
629                .await?;
630            if result.rows_affected == 0 {
631                return Err(anyhow!(
632                    "Attempted to edit a message (id: {message_id}) which does not exist anymore."
633                ))?;
634            }
635
636            // we have to fetch the old mentions,
637            // so we don't send a notification when the message has been edited that you are mentioned in
638            let old_mentions = channel_message_mention::Entity::find()
639                .filter(channel_message_mention::Column::MessageId.eq(message_id))
640                .all(&*tx)
641                .await?;
642
643            // remove all existing mentions
644            channel_message_mention::Entity::delete_many()
645                .filter(channel_message_mention::Column::MessageId.eq(message_id))
646                .exec(&*tx)
647                .await?;
648
649            let new_mentions = self.format_mentions_to_entities(message_id, body, mentions)?;
650            if !new_mentions.is_empty() {
651                // insert new mentions
652                channel_message_mention::Entity::insert_many(new_mentions)
653                    .exec(&*tx)
654                    .await?;
655            }
656
657            let mut update_mention_user_ids = HashSet::default();
658            let mut new_mention_user_ids =
659                mentions.iter().map(|m| m.user_id).collect::<HashSet<_>>();
660            // Filter out users that were mentioned before
661            for mention in &old_mentions {
662                if new_mention_user_ids.contains(&mention.user_id.to_proto()) {
663                    update_mention_user_ids.insert(mention.user_id.to_proto());
664                }
665
666                new_mention_user_ids.remove(&mention.user_id.to_proto());
667            }
668
669            let notification_kind_id =
670                self.get_notification_kind_id_by_name("ChannelMessageMention");
671
672            let existing_notifications = notification::Entity::find()
673                .filter(notification::Column::EntityId.eq(message_id))
674                .filter(notification::Column::Kind.eq(notification_kind_id))
675                .all(&*tx)
676                .await?;
677
678            // determine which notifications should be updated or deleted
679            let mut deleted_notification_ids = HashSet::default();
680            let mut updated_mention_notifications = Vec::new();
681            for notification in existing_notifications {
682                if update_mention_user_ids.contains(&notification.recipient_id.to_proto()) {
683                    if let Some(notification) =
684                        self::notifications::model_to_proto(self, notification).log_err()
685                    {
686                        updated_mention_notifications.push(notification);
687                    }
688                } else {
689                    deleted_notification_ids.insert(notification.id);
690                }
691            }
692
693            let mut notifications = Vec::new();
694            for mentioned_user in new_mention_user_ids {
695                notifications.extend(
696                    self.create_notification(
697                        UserId::from_proto(mentioned_user),
698                        rpc::Notification::ChannelMessageMention {
699                            message_id: message_id.to_proto(),
700                            sender_id: user_id.to_proto(),
701                            channel_id: channel_id.to_proto(),
702                        },
703                        false,
704                        &tx,
705                    )
706                    .await?,
707                );
708            }
709
710            Ok(UpdatedChannelMessage {
711                message_id,
712                participant_connection_ids,
713                notifications,
714                reply_to_message_id: channel_message.reply_to_message_id,
715                timestamp: channel_message.sent_at,
716                deleted_mention_notification_ids: deleted_notification_ids
717                    .into_iter()
718                    .collect::<Vec<_>>(),
719                updated_mention_notifications,
720            })
721        })
722        .await
723    }
724}