//! Bookmark CRUD operations on `MessageRepo`. use chrono::Utc; use uuid::Uuid; use crate::ImksResult; use crate::models::message_bookmark::MessageBookmark; use super::message_repo::MessageRepo; use super::pagination::{CursorPage, clamp_limit}; impl MessageRepo { /// Add a bookmark for a message. pub async fn add_bookmark( &self, message_id: Uuid, channel_id: Uuid, user_id: Uuid, note: Option<&str>, ) -> ImksResult { let id = Uuid::now_v7(); let now = Utc::now(); sqlx::query_as::<_, MessageBookmark>( r#" INSERT INTO message_bookmark (id, message_id, channel_id, user_id, note, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $6) ON CONFLICT (user_id, message_id) DO UPDATE SET note = EXCLUDED.note, updated_at = EXCLUDED.updated_at RETURNING * "#, ) .bind(id) .bind(message_id) .bind(channel_id) .bind(user_id) .bind(note) .bind(now) .fetch_one(self.pool()) .await .map_err(Into::into) } /// Remove a bookmark. pub async fn remove_bookmark(&self, message_id: Uuid, user_id: Uuid) -> ImksResult { let result = sqlx::query("DELETE FROM message_bookmark WHERE message_id = $1 AND user_id = $2") .bind(message_id) .bind(user_id) .execute(self.pool()) .await?; Ok(result.rows_affected() > 0) } /// Check if a user has bookmarked a message. pub async fn is_bookmarked(&self, message_id: Uuid, user_id: Uuid) -> ImksResult { let exists: Option = sqlx::query_scalar( "SELECT id FROM message_bookmark WHERE message_id = $1 AND user_id = $2", ) .bind(message_id) .bind(user_id) .fetch_optional(self.pool()) .await?; Ok(exists.is_some()) } /// List a user's bookmarks with cursor-based pagination (newest first). pub async fn list_bookmarks( &self, user_id: Uuid, before: Option, limit: Option, ) -> ImksResult> { let effective_limit = clamp_limit(limit); let fetch_limit = effective_limit + 1; let rows = match before { Some(cursor) => { sqlx::query_as::<_, MessageBookmark>( r#" SELECT * FROM message_bookmark WHERE user_id = $1 AND id < $2 ORDER BY id DESC LIMIT $3 "#, ) .bind(user_id) .bind(cursor) .bind(fetch_limit) .fetch_all(self.pool()) .await? } None => { sqlx::query_as::<_, MessageBookmark>( r#" SELECT * FROM message_bookmark WHERE user_id = $1 ORDER BY id DESC LIMIT $2 "#, ) .bind(user_id) .bind(fetch_limit) .fetch_all(self.pool()) .await? } }; Ok(CursorPage::from_raw(rows, effective_limit, |b| b.id)) } }