Adding family members

This commit is contained in:
Daniel Flanagan 2024-01-05 01:55:37 -06:00
parent 7694035e31
commit 678596566b
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
10 changed files with 75 additions and 71 deletions

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n create table if not exists 'family_members' (avatar_url text not null, name text not null);\n create table if not exists 'todo' (family_member_name text, 'text' text, done_at datetime);\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "f2d5d7e2aa1946350677f5585e789a1f2fe9721354b5907ff83c4b35e37f3df8"
}

14
Cargo.lock generated
View file

@ -104,6 +104,7 @@ checksum = "d09dbe0e490df5da9d69b36dca48a76635288a82f92eca90024883a56202026d"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core",
"axum-macros",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@ -151,6 +152,18 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-macros"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a2edad600410b905404c594e2523549f1bcd4bded1e252c8f74524ccce0b867"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.69"
@ -245,6 +258,7 @@ dependencies = [
"iana-time-zone", "iana-time-zone",
"js-sys", "js-sys",
"num-traits", "num-traits",
"serde",
"wasm-bindgen", "wasm-bindgen",
"windows-targets 0.48.5", "windows-targets 0.48.5",
] ]

View file

@ -6,8 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
axum = "0.7.3" axum = { version = "0.7.3", features = ["macros"] }
chrono = "0.4.31" chrono = { version = "0.4.31", features = ["serde"] }
maud = { git = "https://github.com/lambda-fairy/maud.git", rev="320add8", features = ["axum"] } maud = { git = "https://github.com/lambda-fairy/maud.git", rev="320add8", features = ["axum"] }
serde = { version = "1.0.194", features = ["derive"] } serde = { version = "1.0.194", features = ["derive"] }
sqlx = { version = "0.7.3", features = ["chrono", "sqlx-sqlite", "sqlite", "runtime-tokio"] } sqlx = { version = "0.7.3", features = ["chrono", "sqlx-sqlite", "sqlite", "runtime-tokio"] }

1
assets/htmx-1.9.10.js Normal file

File diff suppressed because one or more lines are too long

5
build.rs Normal file
View file

@ -0,0 +1,5 @@
// generated by `sqlx migrate build-script`
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
}

BIN
fam.db-shm Normal file

Binary file not shown.

BIN
fam.db-wal Normal file

Binary file not shown.

View file

@ -14,12 +14,14 @@
rust-dev = pkgs.mkShell { rust-dev = pkgs.mkShell {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
sqlite sqlite
sqlx-cli
cargo cargo
rustc rustc
rustfmt rustfmt
rustPackages.clippy rustPackages.clippy
rust-analyzer rust-analyzer
]; ];
DATABASE_URL = "sqlite:fam.db";
}; };
default = outputs.devShells.${system}.rust-dev; default = outputs.devShells.${system}.rust-dev;

View file

@ -0,0 +1,3 @@
-- Add migration script here
create table if not exists 'family_members' (avatar_url text not null, name text not null);
create table if not exists 'todo' (family_member_name text, 'text' text, done_at datetime);

View file

@ -1,73 +1,39 @@
use std::sync::Arc; use axum::extract::State;
use axum::response::Redirect;
use axum::{extract::State, routing::get, Router}; use axum::routing::post;
use axum::Form;
use axum::{routing::get, Router};
use chrono::prelude::*; use chrono::prelude::*;
use chrono::serde::ts_seconds_option;
use maud::{html, Markup, DOCTYPE}; use maud::{html, Markup, DOCTYPE};
use sqlx::{Pool, Sqlite}; use serde::{Deserialize, Serialize};
use sqlx::{query, query_as, SqlitePool};
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use tracing::{debug, info, instrument, trace}; use tracing::{debug, info, instrument, trace};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
struct AppState {
dbpool: Pool<Sqlite>,
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
tracing_subscriber::registry() tracing_subscriber::registry()
.with( .with(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "example_static_file_server=debug,tower_http=debug".into()), .unwrap_or_else(|_| "debug".into()),
) )
.with(tracing_subscriber::fmt::layer()) .with(tracing_subscriber::fmt::layer())
.init(); .init();
let dbpool = sqlx::SqlitePool::connect("fam.db").await.unwrap(); let dbpool = SqlitePool::connect("fam.db").await.unwrap();
let _ = sqlx::migrate!().run(&dbpool).await;
// let countries = sqlx::query!(
// "
// SELECT country, COUNT(*) as count
// FROM users
// GROUP BY country
// WHERE organization = ?
// ",
// organization
// )
// .fetch_all(&dbpool) // -> Vec<{ country: String, count: i64 }>
// .await?;
let _ = sqlx::query!(
r#"
create table if not exists 'family_members' (avatar_url text not null, name text not null);
create table if not exists 'todo' (family_member_name text, 'text' text, done_at datetime);
"#
)
.fetch_all(&dbpool)
.await
.unwrap();
// let countries = sqlx::query!(
// "
// SELECT country, COUNT(*) as count
// FROM users
// GROUP BY country
// WHERE organization = ?
// ",
// organization
// )
// .fetch_all(&dbpool) // -> Vec<{ country: String, count: i64 }>
// .await?;
let state = Arc::new(AppState { dbpool });
debug!("Debug showing"); debug!("Debug showing");
trace!("Trace showing"); trace!("Trace showing");
// build our application with a route // build our application with a route
let app = Router::new() let app = Router::new()
.with_state(state)
.route("/", get(hello_world)) .route("/", get(hello_world))
.route("/admin", get(admin)) .route("/admin", get(admin))
.route("/admin/new-list", post(new_list))
.with_state(dbpool)
.nest_service("/assets", ServeDir::new("assets")); .nest_service("/assets", ServeDir::new("assets"));
let bind = "0.0.0.0:3000"; let bind = "0.0.0.0:3000";
@ -76,17 +42,18 @@ async fn main() {
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow, Debug, Serialize, Deserialize)]
struct FamilyMember { struct FamilyMember {
avatar_url: String, avatar_url: String,
name: String, name: String,
} }
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow, Debug, Serialize, Deserialize)]
struct Todo { struct Todo {
emoji: char, emoji: char,
text: String, text: String,
done_at: Option<DateTime<Local>>, #[serde(with = "ts_seconds_option")]
done_at: Option<DateTime<Utc>>,
} }
fn head() -> Markup { fn head() -> Markup {
@ -96,7 +63,7 @@ fn head() -> Markup {
meta charset="utf-8" {} meta charset="utf-8" {}
meta name="viewport" content="width=device-width, initial-scale=1" {} meta name="viewport" content="width=device-width, initial-scale=1" {}
link rel="stylesheet" href="/assets/style.css" {} link rel="stylesheet" href="/assets/style.css" {}
// TODO: add htmx script src="/assets/htmx-1.9.10.js" {}
title { "Homeman" } title { "Homeman" }
} }
} }
@ -115,25 +82,13 @@ async fn hello_world() -> Markup {
} }
} }
async fn admin(State(state): State<AppState>) -> Markup { async fn admin(State(dbpool): State<SqlitePool>) -> Markup {
// query data and show it trace!("Admin page accessed");
// let countries = sqlx::query!( let family_members: Vec<FamilyMember> = query_as!(
// "
// SELECT country, COUNT(*) as count
// FROM users
// GROUP BY country
// WHERE organization = ?
// ",
// organization
// )
// .fetch_all(&dbpool) // -> Vec<{ country: String, count: i64 }>
// .await?;
trace!("Hello world page accessed");
let family_members: Vec<FamilyMember> = sqlx::query_as!(
FamilyMember, FamilyMember,
"select name, avatar_url from 'family_members';" "select name, avatar_url from 'family_members';"
) )
.fetch_all(&state.dbpool) .fetch_all(&dbpool)
.await .await
.unwrap(); .unwrap();
html! { html! {
@ -146,10 +101,11 @@ async fn admin(State(state): State<AppState>) -> Markup {
li { (fam.name) } li { (fam.name) }
} }
} }
form hx-post="/admin/new-list" { form action="/admin/new-list" method="POST" {
label { label {
"Name" "Name"
input id="name" name="name" type="text" placeholder="John Smith" {} input id="name" name="name" type="text" placeholder="John Smith" {}
input id="avatar_url" name="avatar_url" type="text" placeholder="/assets/yo.png" {}
} }
button type="submit" {"Add Member"} button type="submit" {"Add Member"}
} }
@ -157,4 +113,15 @@ async fn admin(State(state): State<AppState>) -> Markup {
} }
} }
async fn add_todo_list() {} async fn new_list(State(dbpool): State<SqlitePool>, Form(fm): Form<FamilyMember>) -> Redirect {
trace!("New list creation attempt");
let _ = query!(
"insert into family_members (name, avatar_url) values (?1, ?2);",
fm.name,
fm.avatar_url
)
.execute(&dbpool)
.await
.unwrap();
Redirect::to("/admin")
}