//! Message drafts — maps to `message_draft` table. //! //! Stores unsent messages so they survive browser refreshes and sync across //! the user's connected devices. One draft per (channel, user, optional thread). //! Drafts are upserted on every keystroke debounce and deleted on send. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// A user's unsent message draft in a channel. #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct MessageDraft { pub id: Uuid, pub channel_id: Uuid, pub user_id: Uuid, /// Thread the draft belongs to (NULL = top-level). pub thread_id: Option, /// Message this draft is replying to (NULL = new message). pub reply_to_message_id: Option, /// Plain text or markdown body. pub body: String, /// Extensible metadata (attachments to be uploaded, etc.). pub metadata: Option, pub created_at: DateTime, pub updated_at: DateTime, } /// Input for upserting a draft (sent from client on debounced keystroke). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DraftUpsertInput { pub channel_id: Uuid, pub thread_id: Option, pub reply_to_message_id: Option, pub body: String, pub metadata: Option, } #[cfg(test)] mod tests { use super::*; #[test] fn test_draft_upsert_input_serialize() { let input = DraftUpsertInput { channel_id: Uuid::now_v7(), thread_id: None, reply_to_message_id: None, body: "hello, this is a draft".to_string(), metadata: None, }; let json = serde_json::to_value(&input).unwrap(); assert_eq!(json["body"], "hello, this is a draft"); assert!(json["thread_id"].is_null()); } #[test] fn test_draft_serialize() { let draft = MessageDraft { id: Uuid::now_v7(), channel_id: Uuid::now_v7(), user_id: Uuid::now_v7(), thread_id: Some(Uuid::now_v7()), reply_to_message_id: None, body: "draft body".to_string(), metadata: Some(serde_json::json!({"pending_attachments": []})), created_at: Utc::now(), updated_at: Utc::now(), }; let json = serde_json::to_value(&draft).unwrap(); assert_eq!(json["body"], "draft body"); assert!(json["thread_id"].is_string()); assert!(json["metadata"]["pending_attachments"].is_array()); } }