This commit is contained in:
2026-07-25 20:30:24 -04:00
commit 20a6a70c30
9 changed files with 2145 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/target
todo.sqlite
+226
View File
@@ -0,0 +1,226 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "cc"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chunked_transfer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
]
[[package]]
name = "hashlink"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
"hashbrown",
]
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "libsqlite3-sys"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "pkg-config"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rusqlite"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "syn"
version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tiny_http"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
dependencies = [
"ascii",
"chunked_transfer",
"httpdate",
"log",
]
[[package]]
name = "todo"
version = "0.1.0"
dependencies = [
"rusqlite",
"tiny_http",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "todo"
version = "0.1.0"
edition = "2021"
[dependencies]
rusqlite = {version = "0.32.1", features = ["bundled"]}
tiny_http = "0.12.0"
+117
View File
@@ -0,0 +1,117 @@
mod todo_db;
use todo_db::TodoDb;
use tiny_http::{Server, Response, Header};
fn test_task_and_bucket(db: &TodoDb) {
let _ = db.add_task("eat lunch", "", "tomorrow");
let _ = db.add_bucket("today", "schedule");
let _ = db.add_bucket("this week", "schedule");
let _ = db.add_bucket("property research", "project");
}
fn print_db(db: &TodoDb) {
let tasks = db.all_tasks();
let buckets = db.all_buckets();
for b in buckets.iter() {
println!("{}", b.name);
for t in tasks.iter() {
if t.tag.contains(&b.name) {
println!("{}", t.name);
}
}
println!();
}
}
struct Command {
cmd: String,
args: Vec<String>,
}
fn parse_query(query_string: &String) -> Result<Command, String> {
let mut result = Err(format!("No action found"));
for (key, value) in query_string.split('&').map(|s| {
let mut parts = s.splitn(3, '=');
(parts.next().unwrap_or(""), parts.next().unwrap_or(""))
}) {
match key {
"action" => {
println!("found action");
match value {
"show" => {
result = Ok(Command { cmd: format!("show"), args: vec![] });
break;
},
"add" => {
result = Ok(Command { cmd: format!("add"), args: vec![] });
break;
},
"rm" => {
result = Ok(Command { cmd: format!("rm"), args: vec![] });
break;
},
_ => {}
}
}
_ => {}
}
}
result
}
fn handler(cmd: &Command, db: &TodoDb) -> String {
match cmd.cmd.as_str() {
"show" => db.all_tables(),
"add" => {
if let Ok(_) = db.add_task(cmd.args[0].as_str(), "", "") {
return format!("");
}
format!("Failed to add")
},
"rm" => {
if let Ok(_) = db.remove_item(cmd.args[0].as_str(), "") {
return format!("");
}
format!("Failed to remove")
}
_ => format!(""),
}
}
fn main() {
// let args : Vec<String> = std::env::args().collect();
let filename = "todo.sqlite";
// if args.len() < 2 {
// let db = TodoDb::new(true, filename);
// println!("{}", db.all_tables());
// return;
// }
let db_url = "localhost:8001";
let db = TodoDb::new(true, filename);
let server = Server::http(db_url).unwrap();
// one request at a time
for request in server.incoming_requests() {
println!("Incoming request...");
// parse the request
let url = request.url();
let query_string = url.split('?').nth(1).unwrap_or("");
let mut response_body = format!("");
if let Ok(cmd) = parse_query(&format!("{}", query_string)) {
// hit the db
response_body = handler(&cmd, &db);
} else {
println!("problem parsing the query string");
}
// repond to client
let response = Response::from_string(response_body).with_status_code(200);
request.respond(response).unwrap();
}
}
+188
View File
@@ -0,0 +1,188 @@
use rusqlite::{Connection, Result};
pub struct TodoDb {
conn: Connection,
// persistent: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Task {
pub id: u32,
pub name: String,
pub desc: String,
pub tag: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Bucket {
pub id: u32,
pub name: String,
pub category: String,
}
impl TodoDb {
pub fn new(persistent: bool, filename: &str) -> Self {
Self {
conn: TodoDb::init(persistent, filename).unwrap(),
}
}
pub fn init(persistent: bool, filename: &str) -> Result<Connection> {
let conn = if !persistent {
Connection::open_in_memory()?
} else {
Connection::open(filename)? // todo: check for error
};
if let Ok(_) = conn.execute(
"CREATE TABLE task (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
desc TEXT,
tag TEXT
)",
(),
) {}
if let Ok(_) = conn.execute(
"CREATE TABLE bucket (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
category TEXT
)",
(),
) {}
Ok(conn)
}
pub fn add_task(&self, name: &str, desc: &str, tag: &str) -> Result<u32, String> {
let id = self.unique_id(String::from("task"));
println!("Adding task: {:?}", name);
if self.conn.execute(
"INSERT INTO task (id, name, desc, tag) VALUES (?1, ?2, ?3, ?4)",
(id, name, desc, tag),
).is_err() {
return Err(format!("Failed to add task {}", name));
};
Ok(id)
}
pub fn add_bucket(&self, name: &str, category: &str) -> Result<u32, String> {
let id = self.unique_id(String::from("bucket"));
println!("Adding bucket: {:?}", name);
if self.conn.execute(
"INSERT INTO bucket (id, name, category) VALUES (?1, ?2, ?3)",
(id, name, category),
).is_err() {
return Err(format!("Failed to add task {}", name));
};
Ok(id)
}
pub fn remove_item(&self, name: &str, table: &str) -> Result<u32, String> {
println!("deleting: {:?} from {:?}", name, table);
let query = format!("DELETE FROM {} WHERE name = '{}';", table, name);
if self.conn.execute(&query, ()).is_err() {
return Err(format!("Failed to remove {}", name));
};
Ok(0)
}
fn unique_id(&self, table: String) -> u32 {
let query = format!("SELECT id FROM {} ORDER BY id", table);
let mut stmt = self.conn.prepare(&query).unwrap();
let ids_iter = stmt.query_map([], |row| {
Ok(row.get::<_, u32>(0).unwrap())
}).unwrap();
let mut count = 0;
for id in ids_iter {
if count < id.unwrap() {
return count;
}
count += 1;
}
count
}
pub fn all_tasks(&self) -> Vec<Task> {
let mut stmt = self.conn.prepare("SELECT id, name, desc, tag FROM task").unwrap();
let iter = stmt.query_map([], |row| {
Ok(Task {
id: row.get(0)?,
name: row.get(1)?,
desc: row.get(2)?,
tag: row.get(3)?,
})
}).unwrap();
let mut vec = Vec::new();
for i in iter {
vec.push(i.unwrap());
}
vec
}
pub fn all_buckets(&self) -> Vec<Bucket> {
let mut stmt = self.conn.prepare("SELECT id, name, category FROM bucket").unwrap();
let iter = stmt.query_map([], |row| {
Ok(Bucket {
id: row.get(0)?,
name: row.get(1)?,
category: row.get(2)?,
})
}).unwrap();
let mut vec = Vec::new();
for i in iter {
vec.push(i.unwrap());
}
vec
}
// could DB read fail?
pub fn all_tables(&self) -> String {
let mut result = "".to_string();
// table names
let tables = self.table_names();
result += "Tables:\n";
for table in tables {
result += format!("{:?}\n", table).as_str();
}
result += "\n";
// Tasks
result += format!("Tasks:\n").as_str();
let tasks = self.all_tasks();
for task in tasks {
result += format!("{:?}\n", task).as_str();
}
result += "\n";
// buckets
result += format!("Buckets:\n").as_str();
let buckets = self.all_buckets();
for bucket in buckets {
result += format!("{:?}\n", bucket).as_str();
}
result += "\n";
result
}
fn table_names(&self) -> Vec<String> {
let mut stmt = self.conn.prepare(
"SELECT name FROM sqlite_schema WHERE type = 'table'").unwrap();
let tables = stmt.query_map([], |row| {
Ok(row.get::<_,String>(0)?.to_string())
}).unwrap();
let mut vec = Vec::new();
for table in tables {
vec.push(table.unwrap());
}
vec
}
}