//! This crate contains all shared fullstack server functions. use dioxus::prelude::*; use serde::{Deserialize, Serialize}; use std::sync::{Mutex, LazyLock}; use chrono::{DateTime, Utc}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Note { pub id: String, pub title: String, pub content: String, pub tags: Vec, pub created_at: DateTime, pub updated_at: DateTime, pub is_pinned: bool, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Todo { pub id: String, pub title: String, pub completed: bool, pub created_at: DateTime, pub due_date: Option>, } impl Note { pub fn new(title: String, content: String, tags: Vec) -> Self { let now = Utc::now(); Self { id: uuid::Uuid::new_v4().to_string(), title, content, tags, created_at: now, updated_at: now, is_pinned: false, } } } impl Todo { pub fn new(title: String, due_date: Option>) -> Self { let now = Utc::now(); Self { id: uuid::Uuid::new_v4().to_string(), title, completed: false, created_at: now, due_date, } } } type NotesStore = Mutex>; type TodosStore = Mutex>; static NOTES: LazyLock = LazyLock::new(|| { Mutex::new(vec![ Note::new( "Welcome to Braindump".to_string(), "# Welcome\n\nThis is your braindump app. Quickly capture your thoughts, ideas, and notes here.\n\n## Features\n- Quick note capture\n- Markdown support\n- Tag and categorize\n- Search and filter\n- Pin important notes".to_string(), vec!["welcome".to_string(), "guide".to_string()], ), Note::new( "Project Ideas".to_string(), "- Build a personal dashboard\n- Create a habit tracker\n- Develop a recipe manager\n- Make a fitness app\n- Design a reading list organizer".to_string(), vec!["ideas".to_string(), "projects".to_string()], ), Note::new( "Meeting Notes".to_string(), "## Team Sync - Daily\n\nAttendees: Alice, Bob, Charlie\n\nTopics:\n1. Sprint progress review\n2. Blocker discussion\n3. Planning for next week\n\nAction items:\n- [ ] Review PR #123\n- [ ] Update documentation\n- [ ] Schedule follow-up meeting".to_string(), vec!["work".to_string(), "meetings".to_string()], ), ]) }); static TODOS: LazyLock = LazyLock::new(|| { Mutex::new(vec![ Todo::new("Review project proposal".to_string(), Some(Utc::now() + chrono::Duration::days(2))), Todo::new("Complete documentation".to_string(), Some(Utc::now() + chrono::Duration::days(5))), Todo::new("Team meeting preparation".to_string(), Some(Utc::now() + chrono::Duration::days(1))), ]) }); #[post("/api/notes")] pub async fn create_note(title: String, content: String, tags: Vec) -> Result { let mut notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?; let note = Note::new(title, content, tags); notes.push(note.clone()); Ok(note.id) } #[get("/api/notes")] pub async fn list_notes() -> Result, ServerFnError> { let notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?; Ok(notes.clone()) } #[get("/api/notes/:id")] pub async fn get_note(id: String) -> Result { let notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?; notes .iter() .find(|n| n.id == id) .cloned() .ok_or_else(|| ServerFnError::new("Note not found".to_string())) } #[post("/api/notes/:id")] pub async fn update_note(id: String, title: String, content: String, tags: Vec, is_pinned: bool) -> Result<(), ServerFnError> { let mut notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?; if let Some(note) = notes.iter_mut().find(|n| n.id == id) { note.title = title; note.content = content; note.tags = tags; note.is_pinned = is_pinned; note.updated_at = Utc::now(); Ok(()) } else { Err(ServerFnError::new("Note not found".to_string())) } } #[post("/api/notes/:id/delete")] pub async fn delete_note(id: String) -> Result<(), ServerFnError> { let mut notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?; if notes.iter().any(|n| n.id == id) { notes.retain(|n| n.id != id); Ok(()) } else { Err(ServerFnError::new("Note not found".to_string())) } } #[post("/api/notes/:id/pin")] pub async fn toggle_pin_note(id: String) -> Result<(), ServerFnError> { let mut notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?; if let Some(note) = notes.iter_mut().find(|n| n.id == id) { note.is_pinned = !note.is_pinned; note.updated_at = Utc::now(); Ok(()) } else { Err(ServerFnError::new("Note not found".to_string())) } } #[post("/api/echo")] pub async fn echo(message: String) -> Result { Ok(format!("Echo: {}", message)) } // Todo endpoints #[post("/api/todos")] pub async fn create_todo(title: String, due_date: Option) -> Result { let mut todos = TODOS.lock().map_err(|e| ServerFnError::new(e.to_string()))?; let due_date_parsed = due_date.and_then(|d| chrono::DateTime::parse_from_rfc3339(&d).ok()).map(|d| d.with_timezone(&Utc)); let todo = Todo::new(title, due_date_parsed); todos.push(todo.clone()); Ok(todo.id) } #[get("/api/todos")] pub async fn list_todos() -> Result, ServerFnError> { let todos = TODOS.lock().map_err(|e| ServerFnError::new(e.to_string()))?; Ok(todos.clone()) } #[post("/api/todos/:id/toggle")] pub async fn toggle_todo(id: String) -> Result<(), ServerFnError> { let mut todos = TODOS.lock().map_err(|e| ServerFnError::new(e.to_string()))?; if let Some(todo) = todos.iter_mut().find(|t| t.id == id) { todo.completed = !todo.completed; Ok(()) } else { Err(ServerFnError::new("Todo not found".to_string())) } } #[post("/api/todos/:id/delete")] pub async fn delete_todo(id: String) -> Result<(), ServerFnError> { let mut todos = TODOS.lock().map_err(|e| ServerFnError::new(e.to_string()))?; if todos.iter().any(|t| t.id == id) { todos.retain(|t| t.id != id); Ok(()) } else { Err(ServerFnError::new("Todo not found".to_string())) } }