From 10d7cfa739c3dba970f69fe4515018f5c5e2b919 Mon Sep 17 00:00:00 2001 From: skepsismusic Date: Fri, 6 Feb 2026 00:22:14 +0100 Subject: [PATCH] Run cargo fmt on all Rust source files Co-authored-by: Cursor --- backend/src/config.rs | 8 +- backend/src/main.rs | 6 +- backend/src/models/mod.rs | 2 +- backend/src/routes/assets.rs | 37 ++++--- backend/src/routes/daily.rs | 79 +++++++-------- backend/src/routes/mod.rs | 2 +- backend/src/routes/notes.rs | 16 +--- backend/src/routes/projects.rs | 49 ++++++---- backend/src/routes/search.rs | 8 +- backend/src/routes/tasks.rs | 144 ++++++++++++++++++---------- backend/src/services/filesystem.rs | 13 ++- backend/src/services/frontmatter.rs | 10 +- backend/src/services/git.rs | 32 +++---- backend/src/services/markdown.rs | 1 + backend/src/services/mod.rs | 2 +- backend/src/services/search.rs | 23 +++-- backend/src/watcher.rs | 29 +++--- backend/src/websocket.rs | 12 +-- 18 files changed, 273 insertions(+), 200 deletions(-) diff --git a/backend/src/config.rs b/backend/src/config.rs index 52760e8..4ce2974 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -31,10 +31,14 @@ pub fn init_data_dir() { } tracing::info!("Data directory: {}", path.display()); - DATA_DIR.set(path).expect("Data directory already initialized"); + DATA_DIR + .set(path) + .expect("Data directory already initialized"); } /// Get the resolved data directory path. pub fn data_dir() -> &'static Path { - DATA_DIR.get().expect("Data directory not initialized. Call config::init_data_dir() first.") + DATA_DIR + .get() + .expect("Data directory not initialized. Call config::init_data_dir() first.") } diff --git a/backend/src/main.rs b/backend/src/main.rs index 90a6019..ceaf176 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -121,7 +121,11 @@ async fn main() { // Small delay to ensure server is ready tokio::time::sleep(std::time::Duration::from_millis(300)).await; if let Err(e) = webbrowser::open(&url) { - tracing::warn!("Failed to open browser: {}. Open http://localhost:{} manually.", e, port); + tracing::warn!( + "Failed to open browser: {}. Open http://localhost:{} manually.", + e, + port + ); } }); } diff --git a/backend/src/models/mod.rs b/backend/src/models/mod.rs index 4cdf045..9b5c69a 100644 --- a/backend/src/models/mod.rs +++ b/backend/src/models/mod.rs @@ -1,3 +1,3 @@ pub mod note; pub mod project; -pub mod task; \ No newline at end of file +pub mod task; diff --git a/backend/src/routes/assets.rs b/backend/src/routes/assets.rs index 8040524..40dd628 100644 --- a/backend/src/routes/assets.rs +++ b/backend/src/routes/assets.rs @@ -79,7 +79,10 @@ async fn upload_asset( if !is_allowed_content_type(&content_type) { return ( StatusCode::BAD_REQUEST, - format!("Unsupported file type: {}. Only images are allowed.", content_type), + format!( + "Unsupported file type: {}. Only images are allowed.", + content_type + ), ) .into_response(); } @@ -100,7 +103,10 @@ async fn upload_asset( if data.len() > MAX_FILE_SIZE { return ( StatusCode::BAD_REQUEST, - format!("File too large. Maximum size is {} MB.", MAX_FILE_SIZE / 1024 / 1024), + format!( + "File too large. Maximum size is {} MB.", + MAX_FILE_SIZE / 1024 / 1024 + ), ) .into_response(); } @@ -149,7 +155,11 @@ async fn upload_asset( /// Validate that a path component doesn't contain directory traversal fn validate_path_component(component: &str) -> Result<(), String> { - if component.contains("..") || component.contains('/') || component.contains('\\') || component.is_empty() { + if component.contains("..") + || component.contains('/') + || component.contains('\\') + || component.is_empty() + { return Err("Invalid path component".to_string()); } Ok(()) @@ -199,12 +209,7 @@ async fn get_asset(Path((project, filename)): Path<(String, String)>) -> impl In let stream = ReaderStream::new(file); let body = Body::from_stream(stream); - ( - StatusCode::OK, - [(header::CONTENT_TYPE, content_type)], - body, - ) - .into_response() + (StatusCode::OK, [(header::CONTENT_TYPE, content_type)], body).into_response() } fn is_allowed_content_type(content_type: &str) -> bool { @@ -220,11 +225,7 @@ fn is_allowed_content_type(content_type: &str) -> bool { } fn get_content_type(filename: &str) -> &'static str { - let ext = filename - .rsplit('.') - .next() - .unwrap_or("") - .to_lowercase(); + let ext = filename.rsplit('.').next().unwrap_or("").to_lowercase(); match ext.as_str() { "jpg" | "jpeg" => "image/jpeg", @@ -248,7 +249,13 @@ fn generate_unique_filename(dir: &StdPath, original: &str) -> String { // Sanitize filename let sanitized_name: String = name .chars() - .map(|c| if c.is_alphanumeric() || c == '-' || c == '_' { c } else { '_' }) + .map(|c| { + if c.is_alphanumeric() || c == '-' || c == '_' { + c + } else { + '_' + } + }) .collect(); let base_filename = format!("{}{}", sanitized_name, ext); diff --git a/backend/src/routes/daily.rs b/backend/src/routes/daily.rs index c757d38..46e0fc6 100644 --- a/backend/src/routes/daily.rs +++ b/backend/src/routes/daily.rs @@ -1,17 +1,13 @@ use axum::{ - body::Bytes, - extract::Path, - http::StatusCode, - response::IntoResponse, - routing::get, - Json, Router, + body::Bytes, extract::Path, http::StatusCode, response::IntoResponse, routing::get, Json, + Router, }; use chrono::{NaiveDate, Utc}; use serde::{Deserialize, Serialize}; use std::fs; -use crate::services::filesystem; use crate::config; +use crate::services::filesystem; use crate::services::frontmatter; #[derive(Debug, Serialize)] @@ -35,7 +31,12 @@ pub fn router() -> Router { Router::new() .route("/", get(list_daily_notes)) .route("/today", get(get_or_create_today)) - .route("/{date}", get(get_daily_note).post(create_daily_note).put(update_daily_note)) + .route( + "/{date}", + get(get_daily_note) + .post(create_daily_note) + .put(update_daily_note), + ) } /// List all daily notes @@ -52,7 +53,7 @@ async fn list_daily_notes() -> impl IntoResponse { fn list_daily_notes_impl() -> Result, String> { let daily_dir = config::data_dir().join("daily"); - + // Create directory if it doesn't exist if !daily_dir.exists() { fs::create_dir_all(&daily_dir).map_err(|e| e.to_string())?; @@ -70,7 +71,7 @@ fn list_daily_notes_impl() -> Result, String> { } let filename = path.file_stem().and_then(|s| s.to_str()).unwrap_or(""); - + // Validate date format if NaiveDate::parse_from_str(filename, "%Y-%m-%d").is_err() { continue; @@ -102,7 +103,7 @@ fn list_daily_notes_impl() -> Result, String> { /// Get or create today's daily note async fn get_or_create_today() -> impl IntoResponse { let today = Utc::now().format("%Y-%m-%d").to_string(); - + match get_daily_note_impl(&today) { Ok(note) => Json(note).into_response(), Err(_) => { @@ -123,14 +124,16 @@ async fn get_or_create_today() -> impl IntoResponse { async fn get_daily_note(Path(date): Path) -> impl IntoResponse { // Validate date format if NaiveDate::parse_from_str(&date, "%Y-%m-%d").is_err() { - return (StatusCode::BAD_REQUEST, "Invalid date format. Use YYYY-MM-DD").into_response(); + return ( + StatusCode::BAD_REQUEST, + "Invalid date format. Use YYYY-MM-DD", + ) + .into_response(); } match get_daily_note_impl(&date) { Ok(note) => Json(note).into_response(), - Err(err) if err.contains("not found") => { - (StatusCode::NOT_FOUND, err).into_response() - } + Err(err) if err.contains("not found") => (StatusCode::NOT_FOUND, err).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to get daily note: {}", err), @@ -171,16 +174,18 @@ async fn create_daily_note( ) -> impl IntoResponse { // Validate date format if NaiveDate::parse_from_str(&date, "%Y-%m-%d").is_err() { - return (StatusCode::BAD_REQUEST, "Invalid date format. Use YYYY-MM-DD").into_response(); + return ( + StatusCode::BAD_REQUEST, + "Invalid date format. Use YYYY-MM-DD", + ) + .into_response(); } let content = body.and_then(|b| b.content.clone()); match create_daily_note_impl(&date, content.as_deref()) { Ok(note) => (StatusCode::CREATED, Json(note)).into_response(), - Err(err) if err.contains("already exists") => { - (StatusCode::CONFLICT, err).into_response() - } + Err(err) if err.contains("already exists") => (StatusCode::CONFLICT, err).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create daily note: {}", err), @@ -191,7 +196,7 @@ async fn create_daily_note( fn create_daily_note_impl(date: &str, initial_content: Option<&str>) -> Result { let daily_dir = config::data_dir().join("daily"); - + // Create directory if it doesn't exist if !daily_dir.exists() { fs::create_dir_all(&daily_dir).map_err(|e| e.to_string())?; @@ -204,10 +209,9 @@ fn create_daily_note_impl(date: &str, initial_content: Option<&str>) -> Result) -> Result) -> Result, - body: Bytes, -) -> impl IntoResponse { +async fn update_daily_note(Path(date): Path, body: Bytes) -> impl IntoResponse { // Validate date format if NaiveDate::parse_from_str(&date, "%Y-%m-%d").is_err() { - return (StatusCode::BAD_REQUEST, "Invalid date format. Use YYYY-MM-DD").into_response(); + return ( + StatusCode::BAD_REQUEST, + "Invalid date format. Use YYYY-MM-DD", + ) + .into_response(); } let content = String::from_utf8_lossy(&body).to_string(); match update_daily_note_impl(&date, &content) { Ok(note) => Json(note).into_response(), - Err(err) if err.contains("not found") => { - (StatusCode::NOT_FOUND, err).into_response() - } + Err(err) if err.contains("not found") => (StatusCode::NOT_FOUND, err).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to update daily note: {}", err), diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index 9652ed2..eb2b1ec 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -4,4 +4,4 @@ pub mod git; pub mod notes; pub mod projects; pub mod search; -pub mod tasks; \ No newline at end of file +pub mod tasks; diff --git a/backend/src/routes/notes.rs b/backend/src/routes/notes.rs index d11152b..951ddec 100644 --- a/backend/src/routes/notes.rs +++ b/backend/src/routes/notes.rs @@ -1,17 +1,10 @@ -use axum::{ - extract::Path, - http::StatusCode, - response::IntoResponse, - routing::get, - Json, Router, -}; +use axum::{extract::Path, http::StatusCode, response::IntoResponse, routing::get, Json, Router}; use crate::models::note::{Note, NoteSummary}; use crate::services::filesystem; pub fn router() -> Router { - Router::new() - .route("/{id}", get(get_note).put(update_note).delete(delete_note)) + Router::new().route("/{id}", get(get_note).put(update_note).delete(delete_note)) } pub async fn list_notes() -> impl IntoResponse { @@ -50,10 +43,7 @@ pub async fn create_note() -> impl IntoResponse { } } -async fn update_note( - Path(id): Path, - body: String, -) -> impl IntoResponse { +async fn update_note(Path(id): Path, body: String) -> impl IntoResponse { match filesystem::update_note(&id, &body) { Ok(note) => Json::(note).into_response(), Err(err) if err.starts_with("Note not found") => { diff --git a/backend/src/routes/projects.rs b/backend/src/routes/projects.rs index 85ce8db..077d386 100644 --- a/backend/src/routes/projects.rs +++ b/backend/src/routes/projects.rs @@ -8,14 +8,13 @@ use axum::{ use serde::{Deserialize, Serialize}; use std::fs; +use crate::config; use crate::routes::tasks::{ - CreateTaskRequest, UpdateTaskMetaRequest, - list_project_tasks_handler, create_task_handler, get_task_handler, - update_task_content_handler, toggle_task_handler, update_task_meta_handler, - delete_task_handler, + create_task_handler, delete_task_handler, get_task_handler, list_project_tasks_handler, + toggle_task_handler, update_task_content_handler, update_task_meta_handler, CreateTaskRequest, + UpdateTaskMetaRequest, }; use crate::services::filesystem; -use crate::config; use crate::services::frontmatter; #[derive(Debug, Serialize)] @@ -75,15 +74,34 @@ pub fn router() -> Router { Router::new() .route("/", get(list_projects).post(create_project)) .route("/{id}", get(get_project)) - .route("/{id}/content", get(get_project_content).put(update_project_content)) + .route( + "/{id}/content", + get(get_project_content).put(update_project_content), + ) // Task routes (file-based) - .route("/{id}/tasks", get(get_project_tasks).post(create_project_task)) - .route("/{id}/tasks/{task_id}", get(get_project_task).put(update_project_task).delete(delete_project_task)) + .route( + "/{id}/tasks", + get(get_project_tasks).post(create_project_task), + ) + .route( + "/{id}/tasks/{task_id}", + get(get_project_task) + .put(update_project_task) + .delete(delete_project_task), + ) .route("/{id}/tasks/{task_id}/toggle", put(toggle_project_task)) .route("/{id}/tasks/{task_id}/meta", put(update_project_task_meta)) // Note routes - .route("/{id}/notes", get(list_project_notes).post(create_project_note)) - .route("/{id}/notes/{note_id}", get(get_project_note).put(update_project_note).delete(delete_project_note)) + .route( + "/{id}/notes", + get(list_project_notes).post(create_project_note), + ) + .route( + "/{id}/notes/{note_id}", + get(get_project_note) + .put(update_project_note) + .delete(delete_project_note), + ) } // ============ Task Handlers ============ @@ -355,10 +373,7 @@ async fn get_project_content(Path(id): Path) -> impl IntoResponse { } } -async fn update_project_content( - Path(id): Path, - body: String, -) -> impl IntoResponse { +async fn update_project_content(Path(id): Path, body: String) -> impl IntoResponse { let index_path = config::data_dir() .join("projects") .join(&id) @@ -618,7 +633,9 @@ async fn create_project_note( .into_response() } -async fn get_project_note(Path((project_id, note_id)): Path<(String, String)>) -> impl IntoResponse { +async fn get_project_note( + Path((project_id, note_id)): Path<(String, String)>, +) -> impl IntoResponse { let notes_dir = config::data_dir() .join("projects") .join(&project_id) @@ -638,7 +655,7 @@ async fn get_project_note(Path((project_id, note_id)): Path<(String, String)>) - if let Ok(content) = fs::read_to_string(&path) { let (fm, body, _) = frontmatter::parse_frontmatter(&content); - + let file_id = fm .get(&serde_yaml::Value::from("id")) .and_then(|v| v.as_str()) diff --git a/backend/src/routes/search.rs b/backend/src/routes/search.rs index cd9faad..f8caa9f 100644 --- a/backend/src/routes/search.rs +++ b/backend/src/routes/search.rs @@ -1,10 +1,4 @@ -use axum::{ - extract::Query, - http::StatusCode, - response::IntoResponse, - routing::get, - Json, Router, -}; +use axum::{extract::Query, http::StatusCode, response::IntoResponse, routing::get, Json, Router}; use serde::Deserialize; use crate::services::search; diff --git a/backend/src/routes/tasks.rs b/backend/src/routes/tasks.rs index 49bb734..09ff119 100644 --- a/backend/src/routes/tasks.rs +++ b/backend/src/routes/tasks.rs @@ -1,15 +1,10 @@ -use axum::{ - http::StatusCode, - response::IntoResponse, - routing::get, - Json, Router, -}; +use axum::{http::StatusCode, response::IntoResponse, routing::get, Json, Router}; use serde::{Deserialize, Serialize}; use std::fs; use std::path::Path as StdPath; -use crate::services::filesystem; use crate::config; +use crate::services::filesystem; use crate::services::frontmatter; /// Task summary for list views @@ -73,8 +68,7 @@ pub struct UpdateTaskMetaRequest { } pub fn router() -> Router { - Router::new() - .route("/", get(list_all_tasks_handler)) + Router::new().route("/", get(list_all_tasks_handler)) } // ============ Handler Functions (called from projects.rs) ============ @@ -96,7 +90,12 @@ pub async fn create_task_handler( project_id: String, payload: CreateTaskRequest, ) -> impl IntoResponse { - match create_task_impl(&project_id, &payload.title, payload.section.as_deref(), payload.parent_id.as_deref()) { + match create_task_impl( + &project_id, + &payload.title, + payload.section.as_deref(), + payload.parent_id.as_deref(), + ) { Ok(task) => (StatusCode::CREATED, Json(task)).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, @@ -110,9 +109,7 @@ pub async fn create_task_handler( pub async fn get_task_handler(project_id: String, task_id: String) -> impl IntoResponse { match get_task_impl(&project_id, &task_id) { Ok(task) => Json(task).into_response(), - Err(err) if err.contains("not found") => { - (StatusCode::NOT_FOUND, err).into_response() - } + Err(err) if err.contains("not found") => (StatusCode::NOT_FOUND, err).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to get task: {}", err), @@ -129,9 +126,7 @@ pub async fn update_task_content_handler( ) -> impl IntoResponse { match update_task_content_impl(&project_id, &task_id, &body) { Ok(task) => Json(task).into_response(), - Err(err) if err.contains("not found") => { - (StatusCode::NOT_FOUND, err).into_response() - } + Err(err) if err.contains("not found") => (StatusCode::NOT_FOUND, err).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to update task: {}", err), @@ -144,9 +139,7 @@ pub async fn update_task_content_handler( pub async fn toggle_task_handler(project_id: String, task_id: String) -> impl IntoResponse { match toggle_task_impl(&project_id, &task_id) { Ok(task) => Json(task).into_response(), - Err(err) if err.contains("not found") => { - (StatusCode::NOT_FOUND, err).into_response() - } + Err(err) if err.contains("not found") => (StatusCode::NOT_FOUND, err).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to toggle task: {}", err), @@ -163,9 +156,7 @@ pub async fn update_task_meta_handler( ) -> impl IntoResponse { match update_task_meta_impl(&project_id, &task_id, payload) { Ok(task) => Json(task).into_response(), - Err(err) if err.contains("not found") => { - (StatusCode::NOT_FOUND, err).into_response() - } + Err(err) if err.contains("not found") => (StatusCode::NOT_FOUND, err).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to update task metadata: {}", err), @@ -178,9 +169,7 @@ pub async fn update_task_meta_handler( pub async fn delete_task_handler(project_id: String, task_id: String) -> impl IntoResponse { match delete_task_impl(&project_id, &task_id) { Ok(()) => StatusCode::NO_CONTENT.into_response(), - Err(err) if err.contains("not found") => { - (StatusCode::NOT_FOUND, err).into_response() - } + Err(err) if err.contains("not found") => (StatusCode::NOT_FOUND, err).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to delete task: {}", err), @@ -483,11 +472,7 @@ fn toggle_task_impl(project_id: &str, task_id: &str) -> Result { ); // Update section based on completion status - let new_section = if new_completed { - "Completed" - } else { - "Active" - }; + let new_section = if new_completed { "Completed" } else { "Active" }; fm.insert( serde_yaml::Value::from("section"), serde_yaml::Value::from(new_section), @@ -554,14 +539,22 @@ fn toggle_task_impl(project_id: &str, task_id: &str) -> Result { } // Return updated task - let task = parse_task_file(&fs::read_to_string(&task_path).unwrap(), &task_path, project_id) - .ok_or_else(|| "Failed to parse updated task".to_string())?; + let task = parse_task_file( + &fs::read_to_string(&task_path).unwrap(), + &task_path, + project_id, + ) + .ok_or_else(|| "Failed to parse updated task".to_string())?; Ok(task) } -fn calculate_next_due_date(current_due: Option<&str>, recurrence: &str, interval: i64) -> Option { - use chrono::{NaiveDate, Duration, Utc, Months}; +fn calculate_next_due_date( + current_due: Option<&str>, + recurrence: &str, + interval: i64, +) -> Option { + use chrono::{Duration, Months, NaiveDate, Utc}; let base_date = if let Some(due_str) = current_due { NaiveDate::parse_from_str(due_str, "%Y-%m-%d").unwrap_or_else(|_| Utc::now().date_naive()) @@ -600,28 +593,73 @@ fn create_recurring_task_impl( let id = format!("{}-{}", project_id, filename); let mut fm = serde_yaml::Mapping::new(); - fm.insert(serde_yaml::Value::from("id"), serde_yaml::Value::from(id.clone())); - fm.insert(serde_yaml::Value::from("type"), serde_yaml::Value::from("task")); - fm.insert(serde_yaml::Value::from("title"), serde_yaml::Value::from(title)); - fm.insert(serde_yaml::Value::from("completed"), serde_yaml::Value::from(false)); - fm.insert(serde_yaml::Value::from("section"), serde_yaml::Value::from("Active")); - fm.insert(serde_yaml::Value::from("priority"), serde_yaml::Value::from("normal")); - fm.insert(serde_yaml::Value::from("is_active"), serde_yaml::Value::from(true)); - fm.insert(serde_yaml::Value::from("project_id"), serde_yaml::Value::from(project_id)); - fm.insert(serde_yaml::Value::from("recurrence"), serde_yaml::Value::from(recurrence)); - fm.insert(serde_yaml::Value::from("recurrence_interval"), serde_yaml::Value::from(interval as u64)); + fm.insert( + serde_yaml::Value::from("id"), + serde_yaml::Value::from(id.clone()), + ); + fm.insert( + serde_yaml::Value::from("type"), + serde_yaml::Value::from("task"), + ); + fm.insert( + serde_yaml::Value::from("title"), + serde_yaml::Value::from(title), + ); + fm.insert( + serde_yaml::Value::from("completed"), + serde_yaml::Value::from(false), + ); + fm.insert( + serde_yaml::Value::from("section"), + serde_yaml::Value::from("Active"), + ); + fm.insert( + serde_yaml::Value::from("priority"), + serde_yaml::Value::from("normal"), + ); + fm.insert( + serde_yaml::Value::from("is_active"), + serde_yaml::Value::from(true), + ); + fm.insert( + serde_yaml::Value::from("project_id"), + serde_yaml::Value::from(project_id), + ); + fm.insert( + serde_yaml::Value::from("recurrence"), + serde_yaml::Value::from(recurrence), + ); + fm.insert( + serde_yaml::Value::from("recurrence_interval"), + serde_yaml::Value::from(interval as u64), + ); if let Some(due) = due_date { - fm.insert(serde_yaml::Value::from("due_date"), serde_yaml::Value::from(due)); + fm.insert( + serde_yaml::Value::from("due_date"), + serde_yaml::Value::from(due), + ); } if !tags.is_empty() { - let yaml_tags: Vec = tags.iter().map(|t| serde_yaml::Value::from(t.as_str())).collect(); - fm.insert(serde_yaml::Value::from("tags"), serde_yaml::Value::Sequence(yaml_tags)); + let yaml_tags: Vec = tags + .iter() + .map(|t| serde_yaml::Value::from(t.as_str())) + .collect(); + fm.insert( + serde_yaml::Value::from("tags"), + serde_yaml::Value::Sequence(yaml_tags), + ); } - fm.insert(serde_yaml::Value::from("created"), serde_yaml::Value::from(now_str.clone())); - fm.insert(serde_yaml::Value::from("updated"), serde_yaml::Value::from(now_str.clone())); + fm.insert( + serde_yaml::Value::from("created"), + serde_yaml::Value::from(now_str.clone()), + ); + fm.insert( + serde_yaml::Value::from("updated"), + serde_yaml::Value::from(now_str.clone()), + ); let body = format!("# {}\n\n", title); let content = frontmatter::serialize_frontmatter(&fm, &body)?; @@ -728,8 +766,12 @@ fn update_task_meta_impl( filesystem::atomic_write(&task_path, new_content.as_bytes())?; // Return updated task - let task = parse_task_file(&fs::read_to_string(&task_path).unwrap(), &task_path, project_id) - .ok_or_else(|| "Failed to parse updated task".to_string())?; + let task = parse_task_file( + &fs::read_to_string(&task_path).unwrap(), + &task_path, + project_id, + ) + .ok_or_else(|| "Failed to parse updated task".to_string())?; Ok(task) } diff --git a/backend/src/services/filesystem.rs b/backend/src/services/filesystem.rs index 5f2173c..5ce5732 100644 --- a/backend/src/services/filesystem.rs +++ b/backend/src/services/filesystem.rs @@ -60,7 +60,9 @@ fn is_note_file(path: &Path) -> bool { } // data/projects/*/index.md - if path_str.contains("projects") && path.file_name().and_then(|s| s.to_str()) == Some("index.md") { + if path_str.contains("projects") + && path.file_name().and_then(|s| s.to_str()) == Some("index.md") + { return true; } @@ -123,7 +125,10 @@ pub fn normalize_path(path: &Path) -> String { } else { &path_str }; - stripped.replace('\\', "/").trim_start_matches('/').to_string() + stripped + .replace('\\', "/") + .trim_start_matches('/') + .to_string() } /// Read a full note by deterministic ID. @@ -326,9 +331,7 @@ pub fn atomic_write(path: &Path, contents: &[u8]) -> Result<(), String> { let parent = path.parent().ok_or("Invalid path")?; let temp_name = format!( ".{}.tmp", - path.file_name() - .and_then(|s| s.to_str()) - .unwrap_or("file") + path.file_name().and_then(|s| s.to_str()).unwrap_or("file") ); let temp_path = parent.join(temp_name); diff --git a/backend/src/services/frontmatter.rs b/backend/src/services/frontmatter.rs index 9c11423..2278dea 100644 --- a/backend/src/services/frontmatter.rs +++ b/backend/src/services/frontmatter.rs @@ -165,8 +165,14 @@ mod tests { let (fm, body, has_fm) = parse_frontmatter(content); assert!(has_fm); - assert_eq!(fm.get(&Value::from("id")).unwrap().as_str().unwrap(), "test"); - assert_eq!(fm.get(&Value::from("title")).unwrap().as_str().unwrap(), "Test Note"); + assert_eq!( + fm.get(&Value::from("id")).unwrap().as_str().unwrap(), + "test" + ); + assert_eq!( + fm.get(&Value::from("title")).unwrap().as_str().unwrap(), + "Test Note" + ); assert!(body.contains("Body content")); } diff --git a/backend/src/services/git.rs b/backend/src/services/git.rs index ba5c158..89d5466 100644 --- a/backend/src/services/git.rs +++ b/backend/src/services/git.rs @@ -161,8 +161,7 @@ pub fn get_status() -> Result { Some(CommitInfo { id: commit.id().to_string()[..8].to_string(), message: commit.message()?.trim().to_string(), - timestamp: chrono::DateTime::from_timestamp(commit.time().seconds(), 0)? - .to_rfc3339(), + timestamp: chrono::DateTime::from_timestamp(commit.time().seconds(), 0)?.to_rfc3339(), }) }); @@ -311,14 +310,16 @@ pub fn push_to_remote() -> Result<(), String> { .map_err(|e| format!("Remote 'origin' not found: {}", e))?; // Check if remote URL is configured - let remote_url = remote.url().ok_or_else(|| "No remote URL configured".to_string())?; + let remote_url = remote + .url() + .ok_or_else(|| "No remote URL configured".to_string())?; if remote_url.is_empty() { return Err("No remote URL configured".to_string()); } // Create callbacks for authentication let mut callbacks = git2::RemoteCallbacks::new(); - + // Try to use credential helper from git config callbacks.credentials(|_url, username_from_url, _allowed_types| { // Try SSH agent first @@ -402,9 +403,7 @@ pub fn get_log(limit: Option) -> Result, String> { let commit_tree = commit.tree().ok(); if let (Some(pt), Some(ct)) = (parent_tree, commit_tree) { - let diff = repo - .diff_tree_to_tree(Some(&pt), Some(&ct), None) - .ok(); + let diff = repo.diff_tree_to_tree(Some(&pt), Some(&ct), None).ok(); diff.map(|d| d.deltas().count()).unwrap_or(0) } else { 0 @@ -418,10 +417,9 @@ pub fn get_log(limit: Option) -> Result, String> { .unwrap_or(0) }; - let timestamp = - chrono::DateTime::from_timestamp(commit.time().seconds(), 0) - .map(|dt| dt.to_rfc3339()) - .unwrap_or_else(|| "Unknown".to_string()); + let timestamp = chrono::DateTime::from_timestamp(commit.time().seconds(), 0) + .map(|dt| dt.to_rfc3339()) + .unwrap_or_else(|| "Unknown".to_string()); commits.push(CommitDetail { id: oid.to_string(), @@ -449,10 +447,7 @@ pub fn get_working_diff() -> Result { let repo = Repository::open(data_path).map_err(|e| format!("Not a git repository: {}", e))?; // Get HEAD tree (or empty tree if no commits) - let head_tree = repo - .head() - .ok() - .and_then(|h| h.peel_to_tree().ok()); + let head_tree = repo.head().ok().and_then(|h| h.peel_to_tree().ok()); // Diff against working directory let diff = repo @@ -495,7 +490,7 @@ fn parse_diff(diff: &git2::Diff) -> Result { for delta_idx in 0..diff.deltas().count() { let delta = diff.get_delta(delta_idx).ok_or("Missing delta")?; - + let path = delta .new_file() .path() @@ -609,10 +604,7 @@ pub fn get_remote_info() -> Result, String> { let (ahead, behind) = if let Some(ref up) = upstream { // Calculate ahead/behind let local_oid = head.target().unwrap_or_else(git2::Oid::zero); - let upstream_oid = up - .get() - .target() - .unwrap_or_else(git2::Oid::zero); + let upstream_oid = up.get().target().unwrap_or_else(git2::Oid::zero); repo.graph_ahead_behind(local_oid, upstream_oid) .unwrap_or((0, 0)) diff --git a/backend/src/services/markdown.rs b/backend/src/services/markdown.rs index e69de29..8b13789 100644 --- a/backend/src/services/markdown.rs +++ b/backend/src/services/markdown.rs @@ -0,0 +1 @@ + diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs index d3d0ca8..41c4db5 100644 --- a/backend/src/services/mod.rs +++ b/backend/src/services/mod.rs @@ -3,4 +3,4 @@ pub mod frontmatter; pub mod git; pub mod locks; pub mod markdown; -pub mod search; \ No newline at end of file +pub mod search; diff --git a/backend/src/services/search.rs b/backend/src/services/search.rs index 23b4f8d..ce9a71b 100644 --- a/backend/src/services/search.rs +++ b/backend/src/services/search.rs @@ -33,7 +33,10 @@ pub fn search_notes(query: &str) -> Result, String> { match search_with_ripgrep(query) { Ok(results) => return Ok(results), Err(e) => { - tracing::debug!("ripgrep not available, falling back to manual search: {}", e); + tracing::debug!( + "ripgrep not available, falling back to manual search: {}", + e + ); } } @@ -46,10 +49,12 @@ fn search_with_ripgrep(query: &str) -> Result, String> { let data_dir_str = config::data_dir().to_string_lossy(); let output = Command::new("rg") .args([ - "--json", // JSON output for parsing - "--ignore-case", // Case insensitive - "--type", "md", // Only markdown files - "--max-count", "5", // Max 5 matches per file + "--json", // JSON output for parsing + "--ignore-case", // Case insensitive + "--type", + "md", // Only markdown files + "--max-count", + "5", // Max 5 matches per file query, &data_dir_str, ]) @@ -88,13 +93,13 @@ fn parse_ripgrep_output(output: &[u8]) -> Result, String> { let normalized_path = normalize_path(path_str); let title = extract_title_from_path(&normalized_path); - let result = results_map.entry(normalized_path.clone()).or_insert_with(|| { - SearchResult { + let result = results_map + .entry(normalized_path.clone()) + .or_insert_with(|| SearchResult { path: normalized_path, title, matches: Vec::new(), - } - }); + }); result.matches.push(SearchMatch { line_number, diff --git a/backend/src/watcher.rs b/backend/src/watcher.rs index 28a57ec..4710113 100644 --- a/backend/src/watcher.rs +++ b/backend/src/watcher.rs @@ -28,15 +28,19 @@ pub async fn start_watcher(ws_state: Arc) -> Result<(), String> { // Watch the data directory let data_path = config::data_dir(); if !data_path.exists() { - return Err(format!("Data directory does not exist: {}", data_path.display())); + return Err(format!( + "Data directory does not exist: {}", + data_path.display() + )); } // We need to keep the debouncer alive, so we'll store it let debouncer = Arc::new(tokio::sync::Mutex::new(debouncer)); - + { let mut d = debouncer.lock().await; - d.watcher().watch(data_path, RecursiveMode::Recursive) + d.watcher() + .watch(data_path, RecursiveMode::Recursive) .map_err(|e| format!("Failed to watch directory: {}", e))?; } @@ -47,7 +51,7 @@ pub async fn start_watcher(ws_state: Arc) -> Result<(), String> { tokio::spawn(async move { // Keep debouncer alive let _debouncer = debouncer; - + while let Some(events) = rx.recv().await { for event in events { process_event(&event, &ws_state_clone); @@ -58,9 +62,9 @@ pub async fn start_watcher(ws_state: Arc) -> Result<(), String> { Ok(()) } +use std::collections::HashMap; /// Track recent saves to avoid notifying about our own changes use std::sync::Mutex; -use std::collections::HashMap; use std::time::Instant; lazy_static::lazy_static! { @@ -113,19 +117,19 @@ fn process_event(event: &DebouncedEvent, ws_state: &WsState) { } let path_str = normalize_path(&paths[0]); - + // Check if this was a recent save by us (within last 2 seconds) if let Ok(mut saves) = RECENT_SAVES.lock() { // Clean up old entries saves.retain(|_, t| t.elapsed().as_secs() < 5); - + if let Some(saved_at) = saves.get(&path_str) { if saved_at.elapsed().as_secs() < 2 { return; // Skip - this was our own save } } } - + let msg = match &event.kind { EventKind::Create(_) => { tracing::info!("External file created: {}", path_str); @@ -150,12 +154,15 @@ fn process_event(event: &DebouncedEvent, ws_state: &WsState) { /// Normalize path for client consumption fn normalize_path(path: &Path) -> String { let path_str = path.to_string_lossy(); - + // Find "data" in the path and strip everything before and including it if let Some(idx) = path_str.find("data") { let stripped = &path_str[idx + 5..]; // Skip "data" + separator - return stripped.replace('\\', "/").trim_start_matches('/').to_string(); + return stripped + .replace('\\', "/") + .trim_start_matches('/') + .to_string(); } - + path_str.replace('\\', "/") } diff --git a/backend/src/websocket.rs b/backend/src/websocket.rs index 1e7c8bb..0dfd667 100644 --- a/backend/src/websocket.rs +++ b/backend/src/websocket.rs @@ -147,11 +147,7 @@ async fn handle_socket(socket: WebSocket, state: Arc) { if let Ok(client_msg) = serde_json::from_str::(&text) { handle_client_message(&state_clone, &client_id_clone, client_msg).await; } else { - tracing::debug!( - "Unknown message from {}: {}", - client_id_clone, - text - ); + tracing::debug!("Unknown message from {}: {}", client_id_clone, text); } } Message::Close(_) => break, @@ -195,7 +191,11 @@ async fn handle_client_message(state: &Arc, client_id: &str, msg: Clien } }; - match state.lock_manager.acquire(&path, client_id, lock_type).await { + match state + .lock_manager + .acquire(&path, client_id, lock_type) + .await + { Ok(lock_info) => { let lock_type_str = match lock_info.lock_type { LockType::Editor => "editor",