2026-02-04 02:08:08 +01:00
//! 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 < String > ,
pub created_at : DateTime < Utc > ,
pub updated_at : DateTime < Utc > ,
pub is_pinned : bool ,
}
2026-02-04 13:29:33 +01:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq) ]
pub struct Todo {
pub id : String ,
pub title : String ,
pub completed : bool ,
pub created_at : DateTime < Utc > ,
pub due_date : Option < DateTime < Utc > > ,
}
2026-02-04 02:08:08 +01:00
impl Note {
pub fn new ( title : String , content : String , tags : Vec < String > ) -> 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 ,
}
}
}
2026-02-04 13:29:33 +01:00
impl Todo {
pub fn new ( title : String , due_date : Option < DateTime < Utc > > ) -> Self {
let now = Utc ::now ( ) ;
Self {
id : uuid ::Uuid ::new_v4 ( ) . to_string ( ) ,
title ,
completed : false ,
created_at : now ,
due_date ,
}
}
}
2026-02-04 02:08:08 +01:00
type NotesStore = Mutex < Vec < Note > > ;
2026-02-04 13:29:33 +01:00
type TodosStore = Mutex < Vec < Todo > > ;
2026-02-04 02:08:08 +01:00
static NOTES : LazyLock < NotesStore > = LazyLock ::new ( | | {
Mutex ::new ( vec! [
Note ::new (
" Welcome to Braindump " . to_string ( ) ,
" # Welcome \n \n This 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 \n Attendees: Alice, Bob, Charlie \n \n Topics: \n 1. Sprint progress review \n 2. Blocker discussion \n 3. Planning for next week \n \n Action items: \n - [ ] Review PR #123 \n - [ ] Update documentation \n - [ ] Schedule follow-up meeting " . to_string ( ) ,
vec! [ " work " . to_string ( ) , " meetings " . to_string ( ) ] ,
) ,
] )
} ) ;
2026-02-04 13:29:33 +01:00
static TODOS : LazyLock < TodosStore > = 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 ) ) ) ,
] )
} ) ;
2026-02-04 02:08:08 +01:00
#[ post( " /api/notes " ) ]
pub async fn create_note ( title : String , content : String , tags : Vec < String > ) -> Result < String , ServerFnError > {
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 < Vec < Note > , 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 < Note , ServerFnError > {
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 < String > , 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 ( ) ) )
}
}
2026-02-04 12:03:21 +01:00
#[ post( " /api/echo " ) ]
pub async fn echo ( message : String ) -> Result < String , ServerFnError > {
Ok ( format! ( " Echo: {} " , message ) )
}
2026-02-04 13:29:33 +01:00
// Todo endpoints
#[ post( " /api/todos " ) ]
pub async fn create_todo ( title : String , due_date : Option < String > ) -> Result < String , ServerFnError > {
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 < Vec < Todo > , 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 ( ) ) )
}
}