adding sync for multiple terminal using global timestamps (copy of #1 because resubmitting did not work) #2

open
opened by rubberducky.guru targeting main from rubberducky.guru/thought-stream-cli: main

Added sync functionality if you have several terminals open. Not much on it's own, but it also makes the timestamp of each messages use the created_at property from the blip record instead of just taking whatever the current time is before displaying it.

Changed files
+24 -21
src
+20 -15
src/client.rs
··· 1 1 use anyhow::{Context, Result}; 2 2 use chrono::Utc; 3 - use reqwest::{Client as HttpClient, header::{HeaderMap, HeaderValue, AUTHORIZATION}}; 3 + use reqwest::{ 4 + header::{HeaderMap, HeaderValue, AUTHORIZATION}, 5 + Client as HttpClient, 6 + }; 4 7 use serde::{Deserialize, Serialize}; 5 8 use serde_json::Value; 6 9 ··· 62 65 63 66 pub async fn login(&mut self, credentials: &Credentials) -> Result<()> { 64 67 let login_url = format!("{}/xrpc/com.atproto.server.createSession", self.base_url); 65 - 68 + 66 69 let request = LoginRequest { 67 70 identifier: credentials.username.clone(), 68 71 password: credentials.password.clone(), 69 72 }; 70 73 71 - let response = self.http_client 74 + let response = self 75 + .http_client 72 76 .post(&login_url) 73 77 .header("Content-Type", "application/json") 74 78 .json(&request) ··· 93 97 } 94 98 95 99 pub async fn publish_blip(&self, content: &str) -> Result<String> { 96 - let session = self.session.as_ref() 100 + let session = self 101 + .session 102 + .as_ref() 97 103 .context("Not authenticated. Please run 'thought login' first.")?; 98 104 105 + let timestamp = Utc::now().to_rfc3339().replace("+00:00", "Z"); 106 + 99 107 let record = BlipRecord { 100 108 record_type: "stream.thought.blip".to_string(), 101 109 content: content.to_string(), 102 - created_at: Utc::now().to_rfc3339().replace("+00:00", "Z"), 110 + created_at: timestamp.clone(), 103 111 }; 104 112 105 113 let request = CreateRecordRequest { 106 114 repo: session.did.clone(), 107 115 collection: "stream.thought.blip".to_string(), 108 - record: serde_json::to_value(&record) 109 - .context("Failed to serialize blip record")?, 116 + record: serde_json::to_value(&record).context("Failed to serialize blip record")?, 110 117 }; 111 118 112 119 let create_url = format!("{}/xrpc/com.atproto.repo.createRecord", self.base_url); 113 - 120 + 114 121 let mut headers = HeaderMap::new(); 115 122 headers.insert( 116 123 AUTHORIZATION, 117 124 HeaderValue::from_str(&format!("Bearer {}", session.access_jwt)) 118 125 .context("Invalid authorization header")?, 119 126 ); 120 - headers.insert( 121 - "Content-Type", 122 - HeaderValue::from_static("application/json"), 123 - ); 127 + headers.insert("Content-Type", HeaderValue::from_static("application/json")); 124 128 125 - let response = self.http_client 129 + let response = self 130 + .http_client 126 131 .post(&create_url) 127 132 .headers(headers) 128 133 .json(&request) ··· 141 146 .await 142 147 .context("Failed to parse create record response")?; 143 148 144 - Ok(create_response.uri) 149 + Ok(timestamp) 145 150 } 146 151 147 152 pub fn is_authenticated(&self) -> bool { ··· 151 156 pub fn get_user_did(&self) -> Option<String> { 152 157 self.session.as_ref().map(|s| s.did.clone()) 153 158 } 154 - } 159 + }
+4 -6
src/tui.rs
··· 21 21 22 22 use crate::client::AtProtoClient; 23 23 24 - #[derive(Debug, Clone, PartialEq)] 24 + #[derive(Debug, Clone)] 25 25 pub struct Message { 26 26 pub handle: String, 27 27 pub content: String, ··· 263 263 "you".to_string(), 264 264 message, 265 265 true, 266 - Some( 267 - DateTime::parse_from_rfc3339(&t) 268 - .unwrap() 269 - .with_timezone(&Utc), 270 - ), 266 + DateTime::parse_from_rfc3339(&t) 267 + .map(|dt| dt.with_timezone(&Utc)) 268 + .ok(), // Parse RFC3339 → UTC, None if invalid (so current timestamp instead) 271 269 )); 272 270 } 273 271 Err(e) => {