Initial braindump app implementation

Create a complete braindump note-taking application with Dioxus 0.7 featuring:
- Note capture with title, content, and tags
- Full CRUD operations (create, read, update, delete)
- Search functionality for notes
- Tag-based filtering
- Note pinning for quick access
- Modern dark theme with purple accents
- Responsive sidebar layout
- Clean card-based note list
- Full-text editor with auto-save hint

Implemented with:
- Dioxus 0.7.1 fullstack for reactive UI and server functions
- Workspace pattern with shared API crate
- In-memory storage using LazyLock
- Server functions for note management

All core features working and ready for testing.
This commit is contained in:
2026-02-04 02:08:08 +01:00
commit bbed451f3e
46 changed files with 8139 additions and 0 deletions

13
packages/api/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "api"
version = "0.1.0"
edition = "2021"
[dependencies]
dioxus = { workspace = true, features = ["fullstack"] }
serde = { version = "1.0", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1.0", features = ["v4", "serde"] }
[features]
server = ["dioxus/server"]

13
packages/api/README.md Normal file
View File

@@ -0,0 +1,13 @@
# API
This crate contains all shared fullstack server functions. This is a great place to place any server-only logic you would like to expose in multiple platforms like a method that accesses your database or a method that sends an email.
This crate will be built twice:
1. Once for the server build with the `dioxus/server` feature enabled
2. Once for the client build with the client feature disabled
During the server build, the server functions will be collected and hosted on a public API for the client to call. During the client build, the server functions will be compiled into the client build.
## Dependencies
Most server dependencies (like sqlx and tokio) will not compile on client platforms like WASM. To avoid building server dependencies on the client, you should add platform specific dependencies under the `server` feature in the [Cargo.toml](../Cargo.toml) file. More details about managing server only dependencies can be found in the [Dioxus guide](https://dioxuslabs.com/learn/0.7/guides/fullstack/managing_dependencies#adding-server-only-dependencies).

115
packages/api/src/lib.rs Normal file
View File

@@ -0,0 +1,115 @@
//! 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,
}
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,
}
}
}
type NotesStore = Mutex<Vec<Note>>;
static NOTES: LazyLock<NotesStore> = 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()],
),
])
});
#[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()))
}
}