chore: initialized repository

This commit is contained in:
Lukas Wölfer
2026-04-08 22:45:47 +02:00
commit 024d64b284
11 changed files with 2483 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
weight_tracker.db*

2321
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

15
Cargo.toml Normal file
View 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"] }

View 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
View 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
View File

@@ -0,0 +1,2 @@
pub mod handlers;
pub mod models;

27
src/main.rs Normal file
View 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
View 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
View 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
View 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
View 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>