Compare commits

7 Commits

Author SHA1 Message Date
Lukas Wölfer
3bfe779425 chore: bump version to 0.1.0
Some checks failed
Rust / build_and_test (push) Failing after 25s
2026-04-11 14:55:08 +02:00
Lukas Wölfer
395099d0ee chore: add static folder to build artifacts 2026-04-11 14:52:52 +02:00
Lukas Wölfer
b922a610e6 feat: usability 2026-04-11 14:48:43 +02:00
Lukas Wölfer
7010aee5b2 feat: automatically run migrations on database file
Some checks failed
Rust / build_and_test (push) Failing after 1m44s
2026-04-11 14:36:42 +02:00
Lukas Wölfer
614f044160 fix: database creation 2026-04-11 14:33:36 +02:00
Lukas Wölfer
5b9ab3f47d feat: using config crate
Some checks failed
Rust / build_and_test (push) Failing after 34s
2026-04-10 23:47:13 +02:00
Lukas Wölfer
b6f03f9efb Add config.toml support for OIDC and server configuration
- Add toml crate dependency for TOML file parsing
- Create config module with Config struct to deserialize config.toml
- Support OIDC URLs (auth_url, token_url, redirect_url), credentials (client_id, client_secret)
- Make server host/port, database URL, and session secret configurable
- Create config.example.toml with all configuration options documented
- Update main.rs to load config.toml with fallback to environment variables
- Maintain backward compatibility with environment variable configuration
2026-04-10 23:45:12 +02:00
12 changed files with 451 additions and 99 deletions

View File

@@ -3,7 +3,7 @@ name: Release
on: on:
push: push:
tags: tags:
- 'v*.*.*' - "v*.*.*"
jobs: jobs:
build_release: build_release:
@@ -22,6 +22,11 @@ jobs:
- name: Build release - name: Build release
run: | run: |
cargo build --release cargo build --release
- name: Package frontend
run: |
zip -r static.zip static
- name: Generate a changelog - name: Generate a changelog
uses: orhun/git-cliff-action@v4 uses: orhun/git-cliff-action@v4
id: git-cliff id: git-cliff
@@ -31,8 +36,10 @@ jobs:
github_token: "" github_token: ""
env: env:
OUTPUT: CHANGELOG.md OUTPUT: CHANGELOG.md
- uses: akkuman/gitea-release-action@v1 - uses: akkuman/gitea-release-action@v1
with: with:
files: |- files: |-
target/release/weight_tracker target/release/weight_tracker
body: Release build for weight_tracker static.zip
body: ${{ steps.git-cliff.outputs.content }}

233
Cargo.lock generated
View File

@@ -37,6 +37,17 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "ahash"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom 0.2.17",
"once_cell",
"version_check",
]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.21" version = "0.2.21"
@@ -225,7 +236,7 @@ dependencies = [
"aes-gcm", "aes-gcm",
"async-trait", "async-trait",
"axum", "axum",
"base64", "base64 0.22.1",
"bytes", "bytes",
"chrono", "chrono",
"cookie", "cookie",
@@ -259,6 +270,12 @@ dependencies = [
"sqlx", "sqlx",
] ]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@@ -280,6 +297,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.0" version = "2.11.0"
@@ -409,6 +432,25 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "config"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca"
dependencies = [
"async-trait",
"json5",
"lazy_static",
"nom",
"pathdiff",
"ron",
"rust-ini",
"serde",
"serde_json",
"toml",
"yaml-rust",
]
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.9.6" version = "0.9.6"
@@ -422,7 +464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"base64", "base64 0.22.1",
"percent-encoding", "percent-encoding",
"rand 0.8.5", "rand 0.8.5",
"subtle", "subtle",
@@ -581,6 +623,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "dlv-list"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
[[package]] [[package]]
name = "dotenvy" name = "dotenvy"
version = "0.15.7" version = "0.15.7"
@@ -882,6 +930,15 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.5" version = "0.14.5"
@@ -1049,7 +1106,7 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [ dependencies = [
"base64", "base64 0.22.1",
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
@@ -1310,6 +1367,17 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "json5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -1343,7 +1411,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.0",
"libc", "libc",
"plain", "plain",
"redox_syscall 0.7.4", "redox_syscall 0.7.4",
@@ -1360,6 +1428,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "litemap" name = "litemap"
version = "0.8.2" version = "0.8.2"
@@ -1425,6 +1499,12 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.2.0" version = "1.2.0"
@@ -1436,6 +1516,16 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "nonempty" name = "nonempty"
version = "0.7.0" version = "0.7.0"
@@ -1500,7 +1590,7 @@ version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
dependencies = [ dependencies = [
"base64", "base64 0.22.1",
"chrono", "chrono",
"getrandom 0.2.17", "getrandom 0.2.17",
"http", "http",
@@ -1532,6 +1622,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "ordered-multimap"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
dependencies = [
"dlv-list",
"hashbrown 0.12.3",
]
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.2.1" version = "2.2.1"
@@ -1561,6 +1661,12 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]] [[package]]
name = "pem-rfc7468" name = "pem-rfc7468"
version = "0.7.0" version = "0.7.0"
@@ -1576,6 +1682,49 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
dependencies = [
"memchr",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
dependencies = [
"pest",
"sha2",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.17" version = "0.2.17"
@@ -1829,7 +1978,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.0",
] ]
[[package]] [[package]]
@@ -1838,7 +1987,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.0",
] ]
[[package]] [[package]]
@@ -1847,7 +1996,7 @@ version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [ dependencies = [
"base64", "base64 0.22.1",
"bytes", "bytes",
"futures-core", "futures-core",
"http", "http",
@@ -1885,7 +2034,7 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [ dependencies = [
"base64", "base64 0.22.1",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
@@ -1933,6 +2082,17 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "ron"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
dependencies = [
"base64 0.13.1",
"bitflags 1.3.2",
"serde",
]
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.9.10" version = "0.9.10"
@@ -1953,6 +2113,16 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rust-ini"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
dependencies = [
"cfg-if",
"ordered-multimap",
]
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.2" version = "2.1.2"
@@ -2077,7 +2247,7 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.0",
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -2277,7 +2447,7 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
dependencies = [ dependencies = [
"base64", "base64 0.22.1",
"bytes", "bytes",
"chrono", "chrono",
"crc", "crc",
@@ -2354,8 +2524,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64", "base64 0.22.1",
"bitflags", "bitflags 2.11.0",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono", "chrono",
@@ -2398,8 +2568,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64", "base64 0.22.1",
"bitflags", "bitflags 2.11.0",
"byteorder", "byteorder",
"chrono", "chrono",
"crc", "crc",
@@ -2516,7 +2686,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.0",
"core-foundation 0.9.4", "core-foundation 0.9.4",
"system-configuration-sys", "system-configuration-sys",
] ]
@@ -2689,6 +2859,15 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.3" version = "0.5.3"
@@ -2711,7 +2890,7 @@ version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.0",
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",
@@ -2789,6 +2968,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.9.0" version = "2.9.0"
@@ -3019,7 +3204,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.0",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap", "indexmap",
"semver", "semver",
@@ -3081,6 +3266,7 @@ dependencies = [
"axum", "axum",
"axum_session", "axum_session",
"axum_session_sqlx", "axum_session_sqlx",
"config",
"oauth2", "oauth2",
"reqwest 0.13.2", "reqwest 0.13.2",
"serde", "serde",
@@ -3534,7 +3720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags", "bitflags 2.11.0",
"indexmap", "indexmap",
"log", "log",
"serde", "serde",
@@ -3570,6 +3756,15 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.2" version = "0.8.2"

View File

@@ -16,4 +16,5 @@ tokio = { version = "1.0", features = ["full"] }
thiserror = "2.0" thiserror = "2.0"
anyhow = "1.0" anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
config = { version = "0.13", features = ["toml"] }
tower-http = { version = "0.6", features = ["fs", "cors"] } tower-http = { version = "0.6", features = ["fs", "cors"] }

20
config.example.toml Normal file
View File

@@ -0,0 +1,20 @@
# OIDC Configuration
[oidc]
client_id = "your_client_id"
client_secret = "your_client_secret"
auth_url = "https://your-provider.com/auth"
token_url = "https://your-provider.com/token"
redirect_url = "http://localhost:3000/auth/callback"
# Server Configuration
[server]
host = "127.0.0.1"
port = 3000
# Database Configuration
[database]
url = "sqlite:weight_tracker.db"
# Session Configuration
[session]
secret = "your_secret_key_that_is_long_enough_so_the_library_does_not_complain"

View File

@@ -14,7 +14,7 @@ if [ "${CONFIRM}" != "Y" ]; then
exit 1 exit 1
fi fi
git commit -am "chore: bump version to ${VERSION}" git commit --allow-empty -am "chore: bump version to ${VERSION}"
git tag -am "Version ${VERSION}" "${VERSION}" git tag -am "Version ${VERSION}" "${VERSION}"
echo Press Y to push commit and tag echo Press Y to push commit and tag

113
src/config.rs Normal file
View File

@@ -0,0 +1,113 @@
use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, basic::BasicClient};
use serde::{Deserialize, Serialize};
use std::path::Path;
use crate::OidcClient;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub oidc: OidcConfig,
pub server: ServerConfig,
pub database: DatabaseConfig,
pub session: SessionConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OidcConfig {
pub client_id: String,
pub client_secret: String,
pub auth_url: String,
pub token_url: String,
pub redirect_url: String,
}
impl OidcConfig {
pub fn to_client(
&self,
) -> OidcClient {
let client_id = ClientId::new(self.client_id.clone());
let client_secret = ClientSecret::new(self.client_secret.clone());
let auth_url = AuthUrl::new(self.auth_url.clone()).unwrap();
let token_url = TokenUrl::new(self.token_url.clone()).unwrap();
let redirect_url = RedirectUrl::new(self.redirect_url.clone()).unwrap();
BasicClient::new(client_id)
.set_client_secret(client_secret)
.set_auth_uri(auth_url)
.set_token_uri(token_url)
.set_redirect_uri(redirect_url)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionConfig {
pub secret: String,
}
impl Config {
/// Load configuration from a TOML file and environment variables
/// config-rs merges file config with environment variables automatically
pub fn load(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let mut builder = config::Config::builder();
// Add default values first
let defaults = Self::defaults();
builder = builder.add_source(
config::Config::try_from(&defaults)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?,
);
// Load from file if it exists
if Path::new(path).exists() {
builder = builder.add_source(config::File::with_name(path).required(false));
}
// Override with environment variables
// Environment variables should be prefixed with the app name and use __ for nesting
// e.g., WEIGHT_TRACKER_OIDC__CLIENT_ID for oidc.client_id
builder = builder.add_source(
config::Environment::with_prefix("WEIGHT_TRACKER")
.try_parsing(true)
.separator("__"),
);
let config = builder.build()?;
let result: Config = config.try_deserialize()?;
Ok(result)
}
/// Get default configuration values
fn defaults() -> Self {
Config {
oidc: OidcConfig {
client_id: "your_client_id".to_string(),
client_secret: "your_client_secret".to_string(),
auth_url: "https://your-provider.com/auth".to_string(),
token_url: "https://your-provider.com/token".to_string(),
redirect_url: "http://localhost:3000/auth/callback".to_string(),
},
server: ServerConfig {
host: "127.0.0.1".to_string(),
port: 3000,
},
database: DatabaseConfig {
url: "sqlite:weight_tracker.db".to_string(),
},
session: SessionConfig {
secret: "your_secret_key_that_is_long_enough_so_the_library_does_not_complain"
.to_string(),
},
}
}
}

View File

@@ -3,8 +3,11 @@ use axum::response::{Html, Redirect};
use axum::{Form, extract::Query, extract::State}; use axum::{Form, extract::Query, extract::State};
use axum_session::Session; use axum_session::Session;
use axum_session_sqlx::SessionSqlitePool; use axum_session_sqlx::SessionSqlitePool;
use oauth2::{AuthorizationCode, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, Scope, TokenResponse, HttpRequest, HttpResponse};
use oauth2::http; use oauth2::http;
use oauth2::{
AuthorizationCode, CsrfToken, HttpRequest, HttpResponse, PkceCodeChallenge, PkceCodeVerifier,
Scope, TokenResponse,
};
use reqwest::Client; use reqwest::Client;
use serde::Deserialize; use serde::Deserialize;
@@ -17,6 +20,7 @@ pub struct IndexTemplate {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct WeightForm { pub struct WeightForm {
date: String, date: String,
time: String,
weight: f64, weight: f64,
} }
@@ -28,7 +32,10 @@ pub async fn index(State(state): State<crate::AppState>) -> Html<String> {
Html(template.render().unwrap()) Html(template.render().unwrap())
} }
pub async fn login(State(state): State<crate::AppState>, session: Session<SessionSqlitePool>) -> Redirect { pub async fn login(
State(state): State<crate::AppState>,
session: Session<SessionSqlitePool>,
) -> Redirect {
// Generate PKCE challenge // Generate PKCE challenge
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
@@ -102,9 +109,7 @@ pub async fn callback(
} }
// Async HTTP client for oauth2 // Async HTTP client for oauth2
async fn async_http_client( async fn async_http_client(req: HttpRequest) -> Result<HttpResponse, reqwest::Error> {
req: HttpRequest,
) -> Result<HttpResponse, reqwest::Error> {
let client = Client::new(); let client = Client::new();
// Convert http::Method to reqwest::Method // Convert http::Method to reqwest::Method
@@ -130,8 +135,7 @@ async fn async_http_client(
let body = response.bytes().await?; let body = response.bytes().await?;
// Construct an http::Response // Construct an http::Response
let mut http_response = http::Response::builder() let mut http_response = http::Response::builder().status(status_code);
.status(status_code);
for (k, v) in headers.iter() { for (k, v) in headers.iter() {
http_response = http_response.header(k, v); http_response = http_response.header(k, v);
@@ -147,7 +151,8 @@ pub async fn input_post(
) -> Result<Html<String>, Redirect> { ) -> Result<Html<String>, Redirect> {
// Check if user is authenticated // Check if user is authenticated
if let Some(user_id) = session.get::<String>("user_id") { if let Some(user_id) = session.get::<String>("user_id") {
super::models::insert_weight(&state.pool, &user_id, &form.date, form.weight) let datetime = format!("{}T{}", form.date, form.time);
super::models::insert_weight(&state.pool, &user_id, &datetime, form.weight)
.await .await
.unwrap(); .unwrap();
let weights = super::models::get_all_weights(&state.pool) let weights = super::models::get_all_weights(&state.pool)
@@ -157,7 +162,7 @@ pub async fn input_post(
for weight in weights { for weight in weights {
html.push_str(&format!( html.push_str(&format!(
"<p>{}: {} kg by {}</p>\n", "<p>{}: {} kg by {}</p>\n",
weight.date, weight.weight, weight.user_id weight.date, weight.weight, &user_id
)); ));
} }
Ok(Html(html)) Ok(Html(html))

View File

@@ -1,5 +1,6 @@
pub mod handlers; pub mod handlers;
pub mod models; pub mod models;
pub mod config;
use axum::{ use axum::{
Router, Router,
@@ -10,10 +11,7 @@ use axum_session_sqlx::SessionSqlitePool;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
#[derive(Clone)] pub type OidcClient = oauth2::Client<
pub struct AppState {
pub pool: SqlitePool,
pub oidc_client: oauth2::Client<
oauth2::StandardErrorResponse<oauth2::basic::BasicErrorResponseType>, oauth2::StandardErrorResponse<oauth2::basic::BasicErrorResponseType>,
oauth2::StandardTokenResponse<oauth2::EmptyExtraTokenFields, oauth2::basic::BasicTokenType>, oauth2::StandardTokenResponse<oauth2::EmptyExtraTokenFields, oauth2::basic::BasicTokenType>,
oauth2::StandardTokenIntrospectionResponse< oauth2::StandardTokenIntrospectionResponse<
@@ -27,7 +25,12 @@ pub struct AppState {
oauth2::EndpointNotSet, oauth2::EndpointNotSet,
oauth2::EndpointNotSet, oauth2::EndpointNotSet,
oauth2::EndpointSet, oauth2::EndpointSet,
>, >;
#[derive(Clone)]
pub struct AppState {
pub pool: SqlitePool,
pub oidc_client: OidcClient,
} }
pub async fn create_app(state: AppState, session_secret: Vec<u8>, pool: SqlitePool) -> Router { pub async fn create_app(state: AppState, session_secret: Vec<u8>, pool: SqlitePool) -> Router {

View File

@@ -1,44 +1,33 @@
use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, basic::BasicClient}; use std::str::FromStr;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::env; use weight_tracker::{AppState, config::Config, create_app};
use weight_tracker::{AppState, create_app};
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let config = Config::load("config.toml").unwrap_or_else(|e| {
eprintln!("Failed to load configuration: {}", e);
eprintln!("Using default values. Set environment variables prefixed with WEIGHT_TRACKER_ to override.");
std::process::exit(1);
});
// Set up database // Set up database
let database_url = "sqlite:weight_tracker.db"; let pool = SqlitePool::connect_with(
let pool = SqlitePool::connect(database_url) sqlx::sqlite::SqliteConnectOptions::from_str(&config.database.url)
.expect("Could not parse database URL")
.create_if_missing(true),
)
.await
.expect("Failed to connect to database");
sqlx::migrate!()
.run(&pool)
.await .await
.expect("Failed to connect to database"); .expect("Could not run database migrations");
// Set up OIDC client // Set up OIDC client
let client_id = let oidc_client = config.oidc.to_client();
ClientId::new(env::var("OIDC_CLIENT_ID").unwrap_or_else(|_| "your_client_id".to_string()));
let client_secret = ClientSecret::new(
env::var("OIDC_CLIENT_SECRET").unwrap_or_else(|_| "your_client_secret".to_string()),
);
let auth_url = AuthUrl::new(
env::var("OIDC_AUTH_URL").unwrap_or_else(|_| "https://your-provider.com/auth".to_string()),
)
.unwrap();
let token_url = TokenUrl::new(
env::var("OIDC_TOKEN_URL")
.unwrap_or_else(|_| "https://your-provider.com/token".to_string()),
)
.unwrap();
let redirect_url = RedirectUrl::new("http://localhost:3000/auth/callback".to_string()).unwrap();
let oidc_client = BasicClient::new(client_id) let secret = config.session.secret.as_bytes().to_vec();
.set_client_secret(client_secret)
.set_auth_uri(auth_url)
.set_token_uri(token_url)
// Set the URL the user will be redirected to after the authorization process.
.set_redirect_uri(redirect_url);
let secret = env::var("SESSION_SECRET")
.unwrap_or_else(|_| "your_secret_key_that_is_long_enough_so_the_library_does_not_complain".to_string())
.as_bytes()
.to_vec();
let app_state = AppState { let app_state = AppState {
pool: pool.clone(), pool: pool.clone(),
@@ -47,9 +36,8 @@ async fn main() {
let app = create_app(app_state, secret, pool).await; let app = create_app(app_state, secret, pool).await;
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") let addr = format!("{}:{}", config.server.host, config.server.port);
.await let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
.unwrap();
println!("listening on {}", listener.local_addr().unwrap()); println!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }

View File

@@ -1,10 +1,46 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Weight Tracker</title> <title>Weight Tracker</title>
<script src="https://unpkg.com/htmx.org@1.9.10"></script> <script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script>
function fillCurrentDateTime() {
// Fill date and time with current values
const now = new Date();
// Format date as YYYY-MM-DD
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
document.getElementById('date').value = `${year}-${month}-${day}`;
// Format time as HH:MM
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
document.getElementById('time').value = `${hours}:${minutes}`;
// Fill weight with the latest weight from the list
const weightElements = document.querySelectorAll('#weights p');
if (weightElements.length > 0) {
const latestWeightText = weightElements[0].textContent;
// Extract weight value from format "YYYY-MM-DD: XX.X kg by username"
const match = latestWeightText.match(/(\d+\.?\d*)\s*kg/);
if (match) {
document.getElementById('weight').value = match[1];
}
}
}
function showInputDialog() {
fillCurrentDateTime();
document.getElementById('inputDialog').showModal();
}
</script>
<link rel="stylesheet" href="/static/css/style.css"> <link rel="stylesheet" href="/static/css/style.css">
</head> </head>
<body> <body>
<h1>Weight Tracker</h1> <h1>Weight Tracker</h1>
<div id="weights"> <div id="weights">
@@ -12,16 +48,20 @@
<p>{{ weight.date }}: {{ weight.weight }} kg by {{ weight.user_id }}</p> <p>{{ weight.date }}: {{ weight.weight }} kg by {{ weight.user_id }}</p>
{% endfor %} {% endfor %}
</div> </div>
<button onclick="document.getElementById('inputDialog').showModal()">Add Weight</button> <button onclick="showInputDialog()">Add Weight</button>
<dialog id="inputDialog"> <dialog id="inputDialog">
<h1>Add Weight</h1> <h1>Add Weight</h1>
<form hx-post="/input" hx-target="#weights" hx-swap="innerHTML" hx-on:htmx:after-request="document.getElementById('inputDialog').close()"> <form hx-post="/input" hx-target="#weights" hx-swap="innerHTML"
hx-on:htmx:after-request="document.getElementById('inputDialog').close()">
<label for="date">Date:</label> <label for="date">Date:</label>
<input type="date" id="date" name="date" required><br> <input type="date" id="date" name="date" required><br>
<label for="time">Time:</label>
<input type="time" id="time" name="time" required><br>
<label for="weight">Weight (kg):</label> <label for="weight">Weight (kg):</label>
<input type="number" step="0.1" id="weight" name="weight" required><br> <input type="number" step="0.1" id="weight" name="weight" required><br>
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
</dialog> </dialog>
</body> </body>
</html> </html>

View File

@@ -1,20 +0,0 @@
<!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>

View File

@@ -18,8 +18,8 @@ async fn make_app() -> axum::Router {
Some(TokenUrl::new("http://localhost/token".into()).unwrap()), Some(TokenUrl::new("http://localhost/token".into()).unwrap()),
); );
let state = AppState { pool, oidc_client }; let state = AppState { pool: pool.clone(), oidc_client };
create_app(state, b"01234567890123456789012345678901".to_vec()) create_app(state, b"01234567890123456789012345678901".to_vec(), pool).await
} }
#[tokio::test] #[tokio::test]