chore: initialized repository
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
weight_tracker.db*
|
||||
2321
Cargo.lock
generated
Normal file
2321
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "weight_tracker"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.8"
|
||||
askama = "0.12"
|
||||
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "macros"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tower-http = { version = "0.5", features = ["fs", "cors"] }
|
||||
6
migrations/20260408203423_create_weights_table.sql
Normal file
6
migrations/20260408203423_create_weights_table.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE weights (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
weight REAL NOT NULL
|
||||
);
|
||||
41
src/handlers.rs
Normal file
41
src/handlers.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use axum::response::Html;
|
||||
use askama::Template;
|
||||
use sqlx::SqlitePool;
|
||||
use axum::{extract::State, Form};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
pub struct IndexTemplate {
|
||||
pub weights: Vec<super::models::Weight>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "input.html")]
|
||||
pub struct InputTemplate;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WeightForm {
|
||||
date: String,
|
||||
weight: f64,
|
||||
}
|
||||
|
||||
pub async fn index(State(pool): State<SqlitePool>) -> Html<String> {
|
||||
let weights = super::models::get_all_weights(&pool).await.unwrap_or_default();
|
||||
let template = IndexTemplate { weights };
|
||||
Html(template.render().unwrap())
|
||||
}
|
||||
|
||||
pub async fn input_get() -> Html<String> {
|
||||
let template = InputTemplate;
|
||||
Html(template.render().unwrap())
|
||||
}
|
||||
|
||||
pub async fn input_post(
|
||||
State(pool): State<SqlitePool>,
|
||||
Form(form): Form<WeightForm>,
|
||||
) -> Html<String> {
|
||||
let user_id = "test_user"; // TODO: Implement OIDC to get real user_id
|
||||
super::models::insert_weight(&pool, user_id, &form.date, form.weight).await.unwrap();
|
||||
Html("<p>Weight added successfully!</p>".to_string())
|
||||
}
|
||||
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod handlers;
|
||||
pub mod models;
|
||||
27
src/main.rs
Normal file
27
src/main.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tower_http::services::ServeDir;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Set up database
|
||||
let database_url = "sqlite:weight_tracker.db";
|
||||
let pool = SqlitePool::connect(database_url).await.expect("Failed to connect to database");
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/", get(weight_tracker::handlers::index))
|
||||
.route("/input", get(weight_tracker::handlers::input_get).post(weight_tracker::handlers::input_post))
|
||||
.with_state(pool)
|
||||
.nest_service("/static", ServeDir::new("static"));
|
||||
|
||||
// run it
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||
.await
|
||||
.unwrap();
|
||||
println!("listening on {}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
26
src/models.rs
Normal file
26
src/models.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[derive(Debug, FromRow, Serialize, Deserialize)]
|
||||
pub struct Weight {
|
||||
pub id: i64,
|
||||
pub user_id: String,
|
||||
pub date: String,
|
||||
pub weight: f64,
|
||||
}
|
||||
|
||||
pub async fn get_all_weights(pool: &sqlx::SqlitePool) -> Result<Vec<Weight>, sqlx::Error> {
|
||||
sqlx::query_as::<_, Weight>("SELECT id, user_id, date, weight FROM weights ORDER BY date DESC")
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn insert_weight(pool: &sqlx::SqlitePool, user_id: &str, date: &str, weight: f64) -> Result<i64, sqlx::Error> {
|
||||
let result = sqlx::query("INSERT INTO weights (user_id, date, weight) VALUES (?, ?, ?)")
|
||||
.bind(user_id)
|
||||
.bind(date)
|
||||
.bind(weight)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(result.last_insert_rowid())
|
||||
}
|
||||
6
static/css/style.css
Normal file
6
static/css/style.css
Normal file
@@ -0,0 +1,6 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
17
templates/index.html
Normal file
17
templates/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Weight Tracker</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Weight Tracker</h1>
|
||||
<div id="weights">
|
||||
{% for weight in weights %}
|
||||
<p>{{ weight.date }}: {{ weight.weight }} kg by {{ weight.user_id }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a href="/input">Add Weight</a>
|
||||
</body>
|
||||
</html>
|
||||
20
templates/input.html
Normal file
20
templates/input.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Add Weight</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Add Weight</h1>
|
||||
<form hx-post="/input" hx-target="#result" hx-swap="innerHTML">
|
||||
<label for="date">Date:</label>
|
||||
<input type="date" id="date" name="date" required><br>
|
||||
<label for="weight">Weight (kg):</label>
|
||||
<input type="number" step="0.1" id="weight" name="weight" required><br>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
<div id="result"></div>
|
||||
<a href="/">Back to Tracker</a>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user