1use chrono::Utc;
2use serde_json::{json, Value};
3use sha2::{Digest, Sha256};
4use std::fs;
5use std::path::Path;
6use uuid::Uuid;
7
8pub const CURRENT_SCHEMA: &str = "0.3";
9
10pub fn upgrade_message_schema(msg: &mut Value) -> bool {
13 let mut changed = false;
14
15 let schema = msg["schema_version"].as_str().unwrap_or("0.1");
16
17 if schema != CURRENT_SCHEMA {
18 msg["schema_version"] = json!(CURRENT_SCHEMA);
19 changed = true;
20 }
21
22 if let Some(md_path) = msg["markdown"].as_str() {
23 if msg.get("markdown_meta").is_none() {
24 let md = Path::new(md_path);
25
26 if md.exists() {
27 if let Ok(bytes) = fs::read(md) {
28 let mut hasher = Sha256::new();
29 hasher.update(&bytes);
30
31 let hash = format!("{:x}", hasher.finalize());
32
33 let size = bytes.len();
34
35 let filename = md.file_name().unwrap().to_string_lossy().to_string();
36
37 msg["markdown_meta"] = json!({
38 "hash": hash,
39 "size": size,
40 "filename": filename
41 });
42
43 changed = true;
44 }
45 }
46 }
47 }
48
49 changed
50}
51
52pub const SCHEMA_VERSION: &str = "0.3";
60
61pub fn make_index_metadata() -> Value {
62 json!({
63 "threads": [],
64 "active_thread": null,
65 "current_message": null,
66 "created_at": Utc::now().to_rfc3339(),
67 "schema_version": SCHEMA_VERSION
68 })
69}
70
71pub fn make_conversation_metadata(title: &str, id: &str) -> Value {
72 json!({
73 "id": id,
74 "created_at": Utc::now().to_rfc3339(),
75 "messages": [],
76 "tags": [],
77 "parents": [],
78 "children": [],
79 "title": title,
80 "schema_version": SCHEMA_VERSION
81 })
82}
83
84fn compute_file_hash(path: &Path) -> Option<String> {
85 if !path.exists() {
86 return None;
87 }
88
89 let bytes = fs::read(path).ok()?;
90
91 let mut hasher = Sha256::new();
92 hasher.update(&bytes);
93 let result = hasher.finalize();
94
95 Some(format!("{:x}", result))
96}
97
98fn compute_file_size(path: &Path) -> Option<u64> {
99 fs::metadata(path).map(|m| m.len()).ok()
100}
101
102pub fn make_message_metadata(
103 avatar: &str,
104 text: Option<String>,
105 markdown: Option<String>,
106 img: Option<String>,
107 parent: Option<String>,
108) -> Value {
109 let id = Uuid::new_v4().to_string();
110 let timestamp = Utc::now().to_rfc3339();
111
112 let markdown_meta = markdown.as_ref().and_then(|p| {
113 let path = Path::new(p);
114
115 let hash = compute_file_hash(path);
116 let size = compute_file_size(path);
117 let filename = path
118 .file_name()
119 .and_then(|f| f.to_str())
120 .map(|s| s.to_string());
121
122 Some(json!({
123 "hash": hash,
124 "size": size,
125 "filename": filename
126 }))
127 });
128
129 json!({
130 "id": id,
131 "avatar": avatar,
132 "timestamp": timestamp,
133 "text": text,
134 "markdown": markdown,
135 "markdown_meta": markdown_meta,
136 "attachment": img,
137 "parent": parent,
138 "children": [],
139 "branches": [],
140 "schema_version": SCHEMA_VERSION
141 })
142}