Compare commits
No commits in common. "713dff67cef5d35eab8c947745269fb8a7103612" and "a182e4e0cb4e8e488b0904769e1626bd3b94e5a5" have entirely different histories.
713dff67ce
...
a182e4e0cb
23 changed files with 202 additions and 2410 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,4 @@
|
|||
result
|
||||
result-bin
|
||||
.direnv
|
||||
|
||||
|
||||
|
|
|
|||
117
Cargo.lock
generated
117
Cargo.lock
generated
|
|
@ -4,9 +4,9 @@ version = 4
|
|||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -20,12 +20,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "askama"
|
||||
version = "0.14.0"
|
||||
|
|
@ -143,9 +137,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
|
|
@ -161,9 +155,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.44"
|
||||
version = "1.2.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
|
||||
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
|
|
@ -224,23 +218,6 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "food-tracker"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
"axum",
|
||||
"chrono",
|
||||
"parking_lot",
|
||||
"rusqlite",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tower-request-id",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
|
|
@ -428,9 +405,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.82"
|
||||
version = "0.3.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
|
||||
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
|
|
@ -594,9 +571,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
@ -816,9 +793,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.108"
|
||||
version = "2.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
|
||||
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -831,6 +808,22 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
|
||||
[[package]]
|
||||
name = "task_counter"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"axum",
|
||||
"chrono",
|
||||
"parking_lot",
|
||||
"rusqlite",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tower-request-id",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
|
|
@ -998,9 +991,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
|
|
@ -1031,9 +1024,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.105"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
|
||||
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
|
|
@ -1043,22 +1036,13 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.105"
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
|
||||
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
|
@ -1066,10 +1050,33 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.105"
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
|
||||
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
[package]
|
||||
name = "food-tracker"
|
||||
name = "task_counter"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
askama = "0.14.0"
|
||||
axum = "0.8.6"
|
||||
chrono = "0.4.42"
|
||||
|
|
|
|||
130
flake.lock
generated
130
flake.lock
generated
|
|
@ -1,80 +1,6 @@
|
|||
{
|
||||
"nodes": {
|
||||
"cargo2nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": [
|
||||
"rust-overlay"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750364353,
|
||||
"narHash": "sha256-l06DIwnB4JHwP1isUUXk85F+AHQkUSUyAWnAmRxXICg=",
|
||||
"owner": "cargo2nix",
|
||||
"repo": "cargo2nix",
|
||||
"rev": "a709c74619e1a2b68ed12bb398e12fbe29d69657",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cargo2nix",
|
||||
"ref": "release-0.12",
|
||||
"repo": "cargo2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1705099185,
|
||||
"narHash": "sha256-SxJenKtvcrKJd0TyJQMO3p6VA7PEp+vmMnmlKFzWMNs=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2bce5ccff0ad7abda23e8bb56434b6877a446694",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "release-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1760580664,
|
||||
"narHash": "sha256-/YdfibIrnqXAL8p5kqCU345mzpHoOtuVIkMiI2pF4Dc=",
|
||||
|
|
@ -90,49 +16,12 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1744536153,
|
||||
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"cargo2nix": "cargo2nix",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"rust-overlay": "rust-overlay",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1761851069,
|
||||
"narHash": "sha256-VHqBFyQdXE10lvBaNCaJSD5xw1WH6Thqq92OIB6MqZo=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "0881bcdf6c34cd3ba558b19d7a74d8ffc9e1fff0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"ref": "stable",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
|
|
@ -148,24 +37,9 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
|
|
|
|||
23
flake.nix
23
flake.nix
|
|
@ -2,35 +2,16 @@
|
|||
inputs = {
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
cargo2nix = {
|
||||
url = "github:cargo2nix/cargo2nix/release-0.12";
|
||||
inputs.rust-overlay.follows = "rust-overlay";
|
||||
};
|
||||
rust-overlay.url = "github:oxalica/rust-overlay/stable";
|
||||
};
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
utils,
|
||||
cargo2nix,
|
||||
rust-overlay,
|
||||
}:
|
||||
utils.lib.eachDefaultSystem (
|
||||
system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [cargo2nix.overlays.default];
|
||||
};
|
||||
|
||||
rustPkgs = pkgs.rustBuilder.makePackageSet {
|
||||
rustVersion = "1.86.0";
|
||||
packageFun = import ./Cargo.nix;
|
||||
};
|
||||
in rec {
|
||||
packages = {
|
||||
food-tracker = rustPkgs.workspace.food-tracker {};
|
||||
default = packages.food-tracker;
|
||||
};
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.cargo
|
||||
|
|
|
|||
BIN
foods.db
BIN
foods.db
Binary file not shown.
|
|
@ -6,4 +6,4 @@ CREATE TABLE IF NOT EXISTS food (
|
|||
target_servings INTEGER NOT NULL DEFAULT 1,
|
||||
actual_servings INTEGER NOT NULL DEFAULT 0,
|
||||
color TEXT NOT NULL DEFAULT 'white'
|
||||
) STRICT;
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
INSERT OR REPLACE INTO
|
||||
day_serving (day, food_id, servings_eaten)
|
||||
VALUES (
|
||||
CURRENT_DATE,
|
||||
?1,
|
||||
MAX(coalesce((
|
||||
SELECT
|
||||
servings_eaten
|
||||
UPDATE
|
||||
food
|
||||
SET
|
||||
actual_servings = MAX(
|
||||
(SELECT
|
||||
actual_servings
|
||||
FROM
|
||||
day_serving
|
||||
food
|
||||
WHERE id = ?1) - 1,
|
||||
0)
|
||||
WHERE
|
||||
food_id = ?1
|
||||
AND day = CURRENT_DATE
|
||||
) - 1,
|
||||
0
|
||||
), 0)
|
||||
) RETURNING servings_eaten;
|
||||
id = ?1
|
||||
RETURNING
|
||||
actual_servings
|
||||
|
|
|
|||
|
|
@ -4,14 +4,9 @@ SELECT
|
|||
name,
|
||||
kc_per_serving,
|
||||
target_servings,
|
||||
coalesce(day_serving.servings_eaten, 0),
|
||||
actual_servings,
|
||||
color
|
||||
FROM
|
||||
food
|
||||
LEFT JOIN
|
||||
day_serving
|
||||
ON
|
||||
day_serving.food_id = food.id
|
||||
AND day_serving.day = CURRENT_DATE
|
||||
WHERE
|
||||
food.id = ?1;
|
||||
id = ?1
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
SELECT
|
||||
food.id,
|
||||
food.portion,
|
||||
food.name,
|
||||
food.kc_per_serving,
|
||||
food.target_servings,
|
||||
coalesce(day_serving.servings_eaten, 0) as servings_eaten,
|
||||
food.color
|
||||
id,
|
||||
portion,
|
||||
name,
|
||||
kc_per_serving,
|
||||
target_servings,
|
||||
actual_servings,
|
||||
color
|
||||
FROM
|
||||
food
|
||||
LEFT JOIN
|
||||
day_serving
|
||||
ON
|
||||
day_serving.food_id = food.id
|
||||
AND coalesce(day_serving.day, CURRENT_DATE) = CURRENT_DATE
|
||||
ORDER BY
|
||||
sort_order, name;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,4 @@
|
|||
SELECT
|
||||
SUM(kc_per_serving * coalesce(servings_eaten, 0)) AS kc,
|
||||
SUM(protein_per_portion * coalesce(servings_eaten, 0)) AS protein,
|
||||
SUM(fiber_per_portion * coalesce(servings_eaten, 0)) AS bs
|
||||
SUM(kc_per_serving * actual_servings) AS kc
|
||||
FROM
|
||||
food
|
||||
LEFT JOIN
|
||||
day_serving
|
||||
ON
|
||||
day_serving.food_id = food.id
|
||||
AND coalesce(day_serving.day, CURRENT_DATE) = CURRENT_DATE;
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
PRAGMA user_version;
|
||||
|
|
@ -1,17 +1,9 @@
|
|||
INSERT OR REPLACE INTO
|
||||
day_serving (day, food_id, servings_eaten)
|
||||
VALUES (
|
||||
CURRENT_DATE,
|
||||
?1,
|
||||
coalesce((
|
||||
SELECT
|
||||
servings_eaten
|
||||
FROM
|
||||
day_serving
|
||||
WHERE
|
||||
food_id = ?1
|
||||
AND day = CURRENT_DATE
|
||||
) + 1,
|
||||
1
|
||||
)
|
||||
) RETURNING servings_eaten;
|
||||
UPDATE
|
||||
food
|
||||
SET actual_servings = (
|
||||
SELECT actual_servings
|
||||
FROM food
|
||||
WHERE id = ?1
|
||||
) + 1
|
||||
WHERE id = ?1
|
||||
RETURNING actual_servings
|
||||
|
|
|
|||
234
src/main.rs
234
src/main.rs
|
|
@ -5,11 +5,11 @@ use axum::{
|
|||
Router,
|
||||
extract::{MatchedPath, Path, State},
|
||||
http::{Request, StatusCode},
|
||||
response::{Html, IntoResponse, Response},
|
||||
response::Html,
|
||||
routing::{get, post},
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use rusqlite::{CachedStatement, Connection, Row, Transaction};
|
||||
use rusqlite::{CachedStatement, Connection, Row};
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tower_request_id::{RequestId, RequestIdLayer};
|
||||
use tracing::{debug, error, info, info_span};
|
||||
|
|
@ -18,8 +18,8 @@ use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _
|
|||
#[derive(Debug)]
|
||||
struct Sum {
|
||||
kc: i32,
|
||||
bs: f32,
|
||||
protein: f32,
|
||||
bs: i32,
|
||||
protein: i32,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Sum {
|
||||
|
|
@ -56,7 +56,7 @@ struct Food {
|
|||
name: String,
|
||||
kc_per_serving: i32,
|
||||
target_servings: i32,
|
||||
servings_eaten: i32,
|
||||
actual_servings: i32,
|
||||
color: String,
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ impl Food {
|
|||
name: row.get(2)?,
|
||||
kc_per_serving: row.get(3)?,
|
||||
target_servings: row.get(4)?,
|
||||
servings_eaten: row.get(5)?,
|
||||
actual_servings: row.get(5)?,
|
||||
color: row.get(6)?,
|
||||
})
|
||||
}
|
||||
|
|
@ -80,7 +80,8 @@ impl Food {
|
|||
struct PreparedStatements {}
|
||||
|
||||
impl<'conn> PreparedStatements {
|
||||
fn check(conn: &Connection) -> anyhow::Result<Self> {
|
||||
fn check(conn: &Connection) -> rusqlite::Result<Self> {
|
||||
conn.prepare_cached(include_str!("create_tables.sql"))?;
|
||||
conn.prepare_cached(include_str!("increase.sql"))?;
|
||||
conn.prepare_cached(include_str!("decrease.sql"))?;
|
||||
conn.prepare_cached(include_str!("get_food.sql"))?;
|
||||
|
|
@ -90,6 +91,11 @@ impl<'conn> PreparedStatements {
|
|||
Ok(PreparedStatements {})
|
||||
}
|
||||
|
||||
fn create_tables(conn: &'conn Connection) -> CachedStatement<'conn> {
|
||||
conn.prepare_cached(include_str!("create_tables.sql"))
|
||||
.expect("cached statement is invalid")
|
||||
}
|
||||
|
||||
fn increase(conn: &'conn Connection) -> CachedStatement<'conn> {
|
||||
conn.prepare_cached(include_str!("increase.sql"))
|
||||
.expect("cached statement is invalid")
|
||||
|
|
@ -121,82 +127,35 @@ impl<'conn> PreparedStatements {
|
|||
}
|
||||
}
|
||||
|
||||
// Make our own error that wraps `anyhow::Error`.
|
||||
struct AppError(anyhow::Error);
|
||||
|
||||
// Tell axum how to convert `AppError` into a response.
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let error = &self.0;
|
||||
error!(?error, "error returned to client");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", self.0),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
|
||||
// `Result<_, AppError>`. That way you don't need to do that manually.
|
||||
impl<E> From<E> for AppError
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
type ConnState = Arc<Mutex<Connection>>;
|
||||
|
||||
fn get_version(tx: &Transaction) -> anyhow::Result<usize> {
|
||||
Ok(tx.query_one(include_str!("get_version.sql"), (), |row| row.get(0))?)
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
// axum logs rejections from built-in extractors with the `axum::rejection`
|
||||
// target, at `TRACE` level. `axum::rejection=trace` enables showing those events
|
||||
format!(
|
||||
"{}=debug,tower_http=debug,axum::rejection=trace",
|
||||
env!("CARGO_CRATE_NAME")
|
||||
)
|
||||
.into()
|
||||
}),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let db_connecion_str = "./foods.db".to_string();
|
||||
debug!(db_connecion_str, "opening database");
|
||||
let conn = Connection::open(db_connecion_str).expect("failed to open database");
|
||||
PreparedStatements::check(&conn).expect("failed to prepare sql statements");
|
||||
if let Err(e) = PreparedStatements::create_tables(&conn).execute(()) {
|
||||
error!(?e, "failed to create tables");
|
||||
panic!("failed to create tables: {:#?}", e);
|
||||
}
|
||||
|
||||
fn get_migrations() -> [&'static str; 5] {
|
||||
[
|
||||
include_str!("migrations/1.sql"),
|
||||
include_str!("migrations/2.sql"),
|
||||
include_str!("migrations/3.sql"),
|
||||
include_str!("migrations/4.sql"),
|
||||
include_str!("migrations/5.sql"),
|
||||
]
|
||||
}
|
||||
|
||||
fn do_migrations(conn: &mut Connection) -> anyhow::Result<()> {
|
||||
let migrations = get_migrations();
|
||||
let num_migrations = migrations.len();
|
||||
let tx = conn.transaction()?;
|
||||
let version = get_version(&tx)?;
|
||||
if version < migrations.len() {
|
||||
info!(
|
||||
migrations_to_apply = num_migrations - version,
|
||||
"need to apply some migrations"
|
||||
);
|
||||
let mut mig_number = version;
|
||||
for migration in migrations.iter().skip(version) {
|
||||
mig_number += 1;
|
||||
info!(mig_number, "applying migration");
|
||||
debug!(migration = migration);
|
||||
tx.execute_batch(migration)?;
|
||||
if get_version(&tx)? != mig_number {
|
||||
panic!(
|
||||
"expected user_version to eq {} after applying migration {}. maybe a missing 'PRAGMA user_version =' ?",
|
||||
mig_number, mig_number
|
||||
);
|
||||
}
|
||||
}
|
||||
tx.commit()?;
|
||||
info!("applied all migrations");
|
||||
} else {
|
||||
info!("no migrations to apply");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn app(conn: Connection) -> axum::Router {
|
||||
Router::new()
|
||||
let app = Router::new()
|
||||
.route("/", get(root))
|
||||
.route("/increase/{id}", post(increase))
|
||||
.route("/decrease/{id}", post(decrease))
|
||||
|
|
@ -222,40 +181,7 @@ fn app(conn: Connection) -> axum::Router {
|
|||
}),
|
||||
)
|
||||
.layer(RequestIdLayer)
|
||||
.with_state(Arc::new(Mutex::new(conn)))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
// axum logs rejections from built-in extractors with the `axum::rejection`
|
||||
// target, at `TRACE` level. `axum::rejection=trace` enables showing those events
|
||||
format!(
|
||||
"{}=debug,tower_http=debug,axum::rejection=trace",
|
||||
env!("CARGO_CRATE_NAME")
|
||||
)
|
||||
.into()
|
||||
}),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let db_connecion_str = "./foods.db".to_string();
|
||||
debug!(db_connecion_str, "opening database");
|
||||
let mut conn = Connection::open(db_connecion_str).expect("failed to open database");
|
||||
|
||||
if let Err(e) = conn.execute(include_str!("create_tables.sql"), ()) {
|
||||
error!(?e, "failed to create tables");
|
||||
panic!("failed to create tables: {:#?}", e);
|
||||
}
|
||||
|
||||
do_migrations(&mut conn).expect("failed to do database migrations");
|
||||
|
||||
PreparedStatements::check(&conn).expect("failed to prepare sql statements");
|
||||
|
||||
let app = app(conn);
|
||||
.with_state(Arc::new(Mutex::new(conn)));
|
||||
|
||||
let address = "0.0.0.0:3001";
|
||||
let listener = tokio::net::TcpListener::bind(address)
|
||||
|
|
@ -267,11 +193,10 @@ async fn main() -> anyhow::Result<()> {
|
|||
.local_addr()
|
||||
.expect("failed to get local listening address")
|
||||
);
|
||||
axum::serve(listener, app).await?;
|
||||
Ok(())
|
||||
axum::serve(listener, app).await
|
||||
}
|
||||
|
||||
fn get_foods(conn: &ConnState) -> anyhow::Result<Vec<Food>> {
|
||||
fn get_foods(conn: &ConnState) -> rusqlite::Result<Vec<Food>> {
|
||||
let conn = conn.lock();
|
||||
let mut stmt = PreparedStatements::get_foods(&conn);
|
||||
let foods: Vec<_> = stmt
|
||||
|
|
@ -281,16 +206,15 @@ fn get_foods(conn: &ConnState) -> anyhow::Result<Vec<Food>> {
|
|||
Ok(foods)
|
||||
}
|
||||
|
||||
fn get_sum(conn: &Arc<Mutex<Connection>>) -> anyhow::Result<Sum> {
|
||||
fn get_sum(conn: &Arc<Mutex<Connection>>) -> rusqlite::Result<Sum> {
|
||||
let conn = conn.lock();
|
||||
let mut stmt = PreparedStatements::get_sum(&conn);
|
||||
let sum = stmt.query_one((), |row| {
|
||||
Ok(Sum {
|
||||
kc: row.get(0)?,
|
||||
bs: row.get(1)?,
|
||||
protein: row.get(2)?,
|
||||
})
|
||||
})?;
|
||||
let kc = stmt.query_one((), |row| row.get(0))?;
|
||||
let sum = Sum {
|
||||
kc,
|
||||
bs: 99,
|
||||
protein: 99,
|
||||
};
|
||||
debug!(?sum);
|
||||
Ok(sum)
|
||||
}
|
||||
|
|
@ -301,15 +225,19 @@ fn get_date() -> String {
|
|||
date
|
||||
}
|
||||
|
||||
async fn root(State(conn): State<ConnState>) -> Result<Html<String>, AppError> {
|
||||
let foods = get_foods(&conn)?;
|
||||
let sum = get_sum(&conn)?;
|
||||
async fn root(State(conn): State<ConnState>) -> Result<Html<String>, StatusCode> {
|
||||
let foods = get_foods(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let sum = get_sum(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let date = get_date();
|
||||
let index = IndexTemplate { foods, sum, date };
|
||||
Ok(Html(index.render()?))
|
||||
Ok(Html(
|
||||
index
|
||||
.render()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn do_increase(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<()> {
|
||||
fn do_increase(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<()> {
|
||||
let conn = conn.lock();
|
||||
let mut stmt = PreparedStatements::increase(&conn);
|
||||
let new: i32 = stmt.query_one((id,), |row| row.get(0))?;
|
||||
|
|
@ -317,7 +245,7 @@ fn do_increase(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_food(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<Food> {
|
||||
fn get_food(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<Food> {
|
||||
let conn = conn.lock();
|
||||
let mut stmt = PreparedStatements::get_food(&conn);
|
||||
let food = stmt.query_one((id,), Food::from_row)?;
|
||||
|
|
@ -328,16 +256,20 @@ fn get_food(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<Food> {
|
|||
async fn increase(
|
||||
State(conn): State<Arc<Mutex<Connection>>>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<Html<String>, AppError> {
|
||||
do_increase(&conn, id)?;
|
||||
let food = get_food(&conn, id)?;
|
||||
let sum = get_sum(&conn)?;
|
||||
) -> Result<Html<String>, StatusCode> {
|
||||
do_increase(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let food = get_food(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let sum = get_sum(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let date = get_date();
|
||||
let update = FoodUpdateTemplate { food, sum, date };
|
||||
Ok(Html(update.render()?))
|
||||
Ok(Html(
|
||||
update
|
||||
.render()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn do_decrease(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<()> {
|
||||
fn do_decrease(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<()> {
|
||||
let conn = conn.lock();
|
||||
let mut stmt = PreparedStatements::decrease(&conn);
|
||||
let new: i32 = stmt.query_one((id,), |row| row.get(0))?;
|
||||
|
|
@ -348,16 +280,20 @@ fn do_decrease(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<()> {
|
|||
async fn decrease(
|
||||
State(conn): State<Arc<Mutex<Connection>>>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<Html<String>, AppError> {
|
||||
do_decrease(&conn, id)?;
|
||||
let food = get_food(&conn, id)?;
|
||||
let sum = get_sum(&conn)?;
|
||||
) -> Result<Html<String>, StatusCode> {
|
||||
do_decrease(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let food = get_food(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let sum = get_sum(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let date = get_date();
|
||||
let update = FoodUpdateTemplate { food, sum, date };
|
||||
Ok(Html(update.render()?))
|
||||
Ok(Html(
|
||||
update
|
||||
.render()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn do_set(conn: &Arc<Mutex<Connection>>, id: i32, amount: i32) -> anyhow::Result<()> {
|
||||
fn do_set(conn: &Arc<Mutex<Connection>>, id: i32, amount: i32) -> rusqlite::Result<()> {
|
||||
let conn = conn.lock();
|
||||
let mut stmt = PreparedStatements::set(&conn);
|
||||
let new: i32 = stmt.query_one((id, amount), |row| row.get(0))?;
|
||||
|
|
@ -368,11 +304,15 @@ fn do_set(conn: &Arc<Mutex<Connection>>, id: i32, amount: i32) -> anyhow::Result
|
|||
async fn set(
|
||||
State(conn): State<Arc<Mutex<Connection>>>,
|
||||
Path((id, amount)): Path<(i32, i32)>,
|
||||
) -> Result<Html<String>, AppError> {
|
||||
do_set(&conn, id, amount)?;
|
||||
let food = get_food(&conn, id)?;
|
||||
let sum = get_sum(&conn)?;
|
||||
) -> Result<Html<String>, StatusCode> {
|
||||
do_set(&conn, id, amount).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let food = get_food(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let sum = get_sum(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let date = get_date();
|
||||
let update = FoodUpdateTemplate { food, sum, date };
|
||||
Ok(Html(update.render()?))
|
||||
Ok(Html(
|
||||
update
|
||||
.render()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
ALTER TABLE
|
||||
food
|
||||
ADD COLUMN
|
||||
portion_weight INTEGER NOT NULL DEFAULT 100 CHECK (portion_weight > 0);
|
||||
|
||||
PRAGMA user_version = 1;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
ALTER TABLE
|
||||
food
|
||||
ADD COLUMN
|
||||
-- per 100g
|
||||
protein REAL NOT NULL DEFAULT 5.0 CHECK (protein > 0);
|
||||
|
||||
ALTER TABLE
|
||||
food
|
||||
ADD COLUMN
|
||||
-- per 100g
|
||||
fiber REAL NOT NULL DEFAULT 5.0 CHECK (fiber > 0);
|
||||
|
||||
ALTER TABLE
|
||||
food
|
||||
ADD COLUMN
|
||||
protein_per_portion REAL NOT NULL GENERATED ALWAYS AS ((protein / 100) * portion_weight) VIRTUAL;
|
||||
|
||||
ALTER TABLE
|
||||
food
|
||||
ADD COLUMN
|
||||
fiber_per_portion REAL NOT NULL GENERATED ALWAYS AS ((fiber / 100) * portion_weight) VIRTUAL;
|
||||
|
||||
PRAGMA user_version = 2;
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
ALTER TABLE
|
||||
food
|
||||
ADD COLUMN
|
||||
sort_order INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
PRAGMA user_version = 3;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
CREATE TABLE "sqlb_temp_table_1" (
|
||||
"id" INTEGER,
|
||||
"portion" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"kc_per_serving" INTEGER NOT NULL DEFAULT 0,
|
||||
"target_servings" INTEGER NOT NULL DEFAULT 1,
|
||||
"actual_servings" INTEGER NOT NULL DEFAULT 0,
|
||||
"color" TEXT NOT NULL DEFAULT 'white',
|
||||
"portion_weight" INTEGER NOT NULL DEFAULT 100 CHECK("portion_weight" > 0),
|
||||
"protein" REAL NOT NULL DEFAULT 5.0 CHECK("protein" >= 0),
|
||||
"fiber" REAL NOT NULL DEFAULT 5.0 CHECK("fiber" >= 0),
|
||||
"protein_per_portion" REAL NOT NULL GENERATED ALWAYS AS (("protein" / 100) * "portion_weight") VIRTUAL,
|
||||
"fiber_per_portion" REAL NOT NULL GENERATED ALWAYS AS (("fiber" / 100) * "portion_weight") VIRTUAL,
|
||||
"sort_order" INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY("id")
|
||||
) STRICT;
|
||||
INSERT INTO "main"."sqlb_temp_table_1" ("actual_servings","color","fiber","id","kc_per_serving","name","portion","portion_weight","protein","sort_order","target_servings") SELECT "actual_servings","color","fiber","id","kc_per_serving","name","portion","portion_weight","protein","sort_order","target_servings" FROM "main"."food";
|
||||
PRAGMA defer_foreign_keys = '1';
|
||||
DROP TABLE "main"."food";
|
||||
ALTER TABLE "main"."sqlb_temp_table_1" RENAME TO "food";
|
||||
PRAGMA defer_foreign_keys = '0';
|
||||
|
||||
PRAGMA user_version = 4;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
CREATE TABLE day_serving (
|
||||
-- ISO-8601
|
||||
day TEXT NOT NULL DEFAULT CURRENT_DATE,
|
||||
food_id INTEGER NOT NULL,
|
||||
servings_eaten INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(day, food_id),
|
||||
FOREIGN KEY(food_id) REFERENCES food(id)
|
||||
) STRICT;
|
||||
|
||||
INSERT INTO
|
||||
day_serving (food_id, servings_eaten)
|
||||
SELECT
|
||||
id,
|
||||
actual_servings
|
||||
FROM
|
||||
food;
|
||||
|
||||
ALTER TABLE
|
||||
food
|
||||
DROP COLUMN
|
||||
actual_servings;
|
||||
|
||||
PRAGMA user_version = 5;
|
||||
17
src/set.sql
17
src/set.sql
|
|
@ -1,9 +1,8 @@
|
|||
INSERT OR REPLACE INTO
|
||||
day_serving (day, food_id, servings_eaten)
|
||||
VALUES (
|
||||
CURRENT_DATE,
|
||||
?1,
|
||||
MAX(?2, 0)
|
||||
)
|
||||
RETURNING servings_eaten;
|
||||
|
||||
UPDATE
|
||||
food
|
||||
SET
|
||||
actual_servings = MAX(?2, 0)
|
||||
WHERE
|
||||
id = ?1
|
||||
RETURNING
|
||||
actual_servings
|
||||
|
|
|
|||
|
|
@ -20,18 +20,18 @@
|
|||
{% for counter in self::range(10) %}
|
||||
{% if loop.index as i32 <= food.target_servings %}
|
||||
<label class="ok">
|
||||
{% if loop.index as i32 <= food.servings_eaten %}
|
||||
<input type="button" hx-post="/set/{{ food.id }}/to/{{ loop.index as i32 - 1}}" hx-target="#card-{{ food.id }}" value="●" class="checked">
|
||||
{% if loop.index as i32 <= food.actual_servings %}
|
||||
<input type="checkbox" checked hx-post="/set/{{ food.id }}/to/{{ loop.index as i32 - 1}}" hx-target="#card-{{ food.id }}">
|
||||
{% else %}
|
||||
<input type="button" hx-post="/set/{{ food.id }}/to/{{ loop.index as i32 }}" hx-target="#card-{{ food.id }}" value="●" class="unchecked">
|
||||
<input type="checkbox" hx-post="/set/{{ food.id }}/to/{{ loop.index as i32 }}" hx-target="#card-{{ food.id }}">
|
||||
{% endif %}
|
||||
</label>
|
||||
{% else %}
|
||||
<label class="bad">
|
||||
{% if loop.index as i32 <= food.servings_eaten %}
|
||||
<input type="button" hx-post="/set/{{ food.id }}/to/{{ loop.index as i32 - 1}}" hx-target="#card-{{ food.id }}" value="●" class="checked">
|
||||
{% if loop.index as i32 <= food.actual_servings %}
|
||||
<input type="checkbox" checked hx-post="/set/{{ food.id }}/to/{{ loop.index as i32 - 1}}" hx-target="#card-{{ food.id }}">
|
||||
{% else %}
|
||||
<input type="button" hx-post="/set/{{ food.id }}/to/{{ loop.index as i32 }}" hx-target="#card-{{ food.id }}" value="●" class="unchecked">
|
||||
<input type="checkbox" hx-post="/set/{{ food.id }}/to/{{ loop.index as i32 }}" hx-target="#card-{{ food.id }}">
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
}
|
||||
|
||||
.card {
|
||||
background-color: #f6edcd;
|
||||
background-color: hsl(from #a8c8a6 h 30% l);
|
||||
}
|
||||
|
||||
hr {
|
||||
|
|
@ -132,22 +132,5 @@
|
|||
accent-color: hsl(from #cb8175 h 90% l);
|
||||
background-color: hsl(from #cb8175 h 60% l);
|
||||
}
|
||||
|
||||
input[type="button"] {
|
||||
display: inline-block;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.unchecked {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
input[type="button"]:hover {
|
||||
background-color: beige;
|
||||
}
|
||||
</style>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue