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