821537186e
- Reorganized import statements in adapter tests for better readability - Replaced or_insert_with(Vec::new) with or_default() in test closures - Updated Cargo.lock with new dependency versions and checksums - Added TLS features to tonic dependency configuration - Included sqlx, chrono, and uuid dependencies with specific features - Added jsonwebtoken and arc-swap as project dependencies - Reformatted assertion statements to comply with line length limits - Adjusted base64 import order in engine codec module - Updated protobuf include statement formatting
114 lines
3.5 KiB
Rust
114 lines
3.5 KiB
Rust
//! Draft CRUD operations on `MessageRepo`.
|
|
//!
|
|
//! One draft per (channel, user, thread). Upserted on every keystroke debounce,
|
|
//! deleted on send. Thread_id=NULL uses a dedicated conflict target.
|
|
|
|
use chrono::Utc;
|
|
use uuid::Uuid;
|
|
|
|
use crate::ImksResult;
|
|
use crate::models::message_draft::MessageDraft;
|
|
|
|
use super::message_repo::MessageRepo;
|
|
|
|
impl MessageRepo {
|
|
/// Upsert a draft for the given (channel, user, thread) key.
|
|
/// Uses NULL-safe conflict handling via COALESCE.
|
|
pub async fn upsert_draft(
|
|
&self,
|
|
channel_id: Uuid,
|
|
user_id: Uuid,
|
|
thread_id: Option<Uuid>,
|
|
body: &str,
|
|
reply_to_message_id: Option<Uuid>,
|
|
metadata: Option<serde_json::Value>,
|
|
) -> ImksResult<MessageDraft> {
|
|
let id = Uuid::now_v7();
|
|
let now = Utc::now();
|
|
|
|
let query = if thread_id.is_some() {
|
|
r#"
|
|
INSERT INTO message_draft (
|
|
id, channel_id, user_id, thread_id, reply_to_message_id,
|
|
body, metadata, created_at, updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)
|
|
ON CONFLICT (channel_id, user_id, thread_id) DO UPDATE SET
|
|
body = EXCLUDED.body,
|
|
reply_to_message_id = EXCLUDED.reply_to_message_id,
|
|
metadata = EXCLUDED.metadata,
|
|
updated_at = EXCLUDED.updated_at
|
|
RETURNING *
|
|
"#
|
|
} else {
|
|
r#"
|
|
INSERT INTO message_draft (
|
|
id, channel_id, user_id, thread_id, reply_to_message_id,
|
|
body, metadata, created_at, updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)
|
|
ON CONFLICT (channel_id, user_id) WHERE thread_id IS NULL DO UPDATE SET
|
|
body = EXCLUDED.body,
|
|
reply_to_message_id = EXCLUDED.reply_to_message_id,
|
|
metadata = EXCLUDED.metadata,
|
|
updated_at = EXCLUDED.updated_at
|
|
RETURNING *
|
|
"#
|
|
};
|
|
|
|
sqlx::query_as::<_, MessageDraft>(query)
|
|
.bind(id)
|
|
.bind(channel_id)
|
|
.bind(user_id)
|
|
.bind(thread_id)
|
|
.bind(reply_to_message_id)
|
|
.bind(body)
|
|
.bind(metadata)
|
|
.bind(now)
|
|
.fetch_one(self.pool())
|
|
.await
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
/// Get a user's draft for a channel (optionally scoped to a thread).
|
|
pub async fn get_draft(
|
|
&self,
|
|
channel_id: Uuid,
|
|
user_id: Uuid,
|
|
thread_id: Option<Uuid>,
|
|
) -> ImksResult<Option<MessageDraft>> {
|
|
sqlx::query_as::<_, MessageDraft>(
|
|
r#"
|
|
SELECT * FROM message_draft
|
|
WHERE channel_id = $1 AND user_id = $2 AND thread_id IS NOT DISTINCT FROM $3
|
|
"#,
|
|
)
|
|
.bind(channel_id)
|
|
.bind(user_id)
|
|
.bind(thread_id)
|
|
.fetch_optional(self.pool())
|
|
.await
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
/// Delete a draft after the message is sent.
|
|
pub async fn delete_draft(
|
|
&self,
|
|
channel_id: Uuid,
|
|
user_id: Uuid,
|
|
thread_id: Option<Uuid>,
|
|
) -> ImksResult<bool> {
|
|
let result = sqlx::query(
|
|
r#"
|
|
DELETE FROM message_draft
|
|
WHERE channel_id = $1 AND user_id = $2 AND thread_id IS NOT DISTINCT FROM $3
|
|
"#,
|
|
)
|
|
.bind(channel_id)
|
|
.bind(user_id)
|
|
.bind(thread_id)
|
|
.execute(self.pool())
|
|
.await?;
|
|
|
|
Ok(result.rows_affected() > 0)
|
|
}
|
|
}
|