steid

@jamesgill /

feat: config from env and SQLite pool in app context

AppConfig reads STEID_-prefixed vars via envy, with defaults so a bare
`cargo run` works. Bind address is deliberately absent — Topcoat owns it
through HOST/PORT, superseding the previous attempt's STEID_LISTEN_ADDR.

The SQLite pool and config register as Topcoat app context; the placeholder
home page reads through the pool to prove the wiring end to end. Layout
follows architecture.md: application/ holds config, infrastructure/ holds
the pool and web handlers.

3 tests, driving envy from an explicit iterator rather than the process
environment so they don't race on shared global state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JamesPatrickGill authored 1 month agoparent3475719Browse files0d12f7df554be6cede324f958da4fbcfb423fb9b

8 files changed+782 −36

Cargo.lock+635 −6View file
@@ -88,12 +88,27 @@ dependencies = [
8888 "tokio",
8989 ]
9090
91+[[package]]
92+name = "atoi"
93+version = "2.0.0"
94+source = "registry+https://github.com/rust-lang/crates.io-index"
95+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
96+dependencies = [
97+ "num-traits",
98+]
99+
91100 [[package]]
92101 name = "atomic-waker"
93102 version = "1.1.2"
94103 source = "registry+https://github.com/rust-lang/crates.io-index"
95104 checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
96105
106+[[package]]
107+name = "autocfg"
108+version = "1.5.1"
109+source = "registry+https://github.com/rust-lang/crates.io-index"
110+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
111+
97112 [[package]]
98113 name = "base64"
99114 version = "0.22.1"
@@ -111,6 +126,9 @@ name = "bitflags"
111126 version = "2.13.1"
112127 source = "registry+https://github.com/rust-lang/crates.io-index"
113128 checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
129+dependencies = [
130+ "serde_core",
131+]
114132
115133 [[package]]
116134 name = "block-buffer"
@@ -163,18 +181,45 @@ version = "3.20.3"
163181 source = "registry+https://github.com/rust-lang/crates.io-index"
164182 checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
165183
184+[[package]]
185+name = "byteorder"
186+version = "1.5.0"
187+source = "registry+https://github.com/rust-lang/crates.io-index"
188+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
189+
166190 [[package]]
167191 name = "bytes"
168192 version = "1.12.1"
169193 source = "registry+https://github.com/rust-lang/crates.io-index"
170194 checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
171195
196+[[package]]
197+name = "cc"
198+version = "1.4.0"
199+source = "registry+https://github.com/rust-lang/crates.io-index"
200+checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
201+dependencies = [
202+ "find-msvc-tools",
203+ "shlex",
204+]
205+
172206 [[package]]
173207 name = "cfg-if"
174208 version = "1.0.4"
175209 source = "registry+https://github.com/rust-lang/crates.io-index"
176210 checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
177211
212+[[package]]
213+name = "chacha20"
214+version = "0.10.1"
215+source = "registry+https://github.com/rust-lang/crates.io-index"
216+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
217+dependencies = [
218+ "cfg-if",
219+ "cpufeatures 0.3.0",
220+ "rand_core 0.10.1",
221+]
222+
178223 [[package]]
179224 name = "cipher"
180225 version = "0.4.4"
@@ -185,6 +230,12 @@ dependencies = [
185230 "inout",
186231 ]
187232
233+[[package]]
234+name = "cmov"
235+version = "0.5.4"
236+source = "registry+https://github.com/rust-lang/crates.io-index"
237+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
238+
188239 [[package]]
189240 name = "compression-codecs"
190241 version = "0.4.38"
@@ -217,8 +268,8 @@ checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
217268 dependencies = [
218269 "aes-gcm",
219270 "base64 0.22.1",
220 "hkdf",
221 "hmac",
271+ "hkdf 0.12.4",
272+ "hmac 0.12.1",
222273 "percent-encoding",
223274 "rand 0.8.7",
224275 "sha2 0.10.9",
@@ -245,6 +296,21 @@ dependencies = [
245296 "libc",
246297 ]
247298
299+[[package]]
300+name = "crc"
301+version = "3.4.0"
302+source = "registry+https://github.com/rust-lang/crates.io-index"
303+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
304+dependencies = [
305+ "crc-catalog",
306+]
307+
308+[[package]]
309+name = "crc-catalog"
310+version = "2.5.0"
311+source = "registry+https://github.com/rust-lang/crates.io-index"
312+checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
313+
248314 [[package]]
249315 name = "crc32fast"
250316 version = "1.5.0"
@@ -254,6 +320,21 @@ dependencies = [
254320 "cfg-if",
255321 ]
256322
323+[[package]]
324+name = "crossbeam-queue"
325+version = "0.3.13"
326+source = "registry+https://github.com/rust-lang/crates.io-index"
327+checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
328+dependencies = [
329+ "crossbeam-utils",
330+]
331+
332+[[package]]
333+name = "crossbeam-utils"
334+version = "0.8.22"
335+source = "registry+https://github.com/rust-lang/crates.io-index"
336+checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
337+
257338 [[package]]
258339 name = "crypto-common"
259340 version = "0.1.7"
@@ -283,6 +364,15 @@ dependencies = [
283364 "cipher",
284365 ]
285366
367+[[package]]
368+name = "ctutils"
369+version = "0.4.2"
370+source = "registry+https://github.com/rust-lang/crates.io-index"
371+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
372+dependencies = [
373+ "cmov",
374+]
375+
286376 [[package]]
287377 name = "data-encoding"
288378 version = "2.11.0"
@@ -315,6 +405,7 @@ dependencies = [
315405 "block-buffer 0.12.1",
316406 "const-oid",
317407 "crypto-common 0.2.2",
408+ "ctutils",
318409 ]
319410
320411 [[package]]
@@ -328,6 +419,21 @@ dependencies = [
328419 "syn 3.0.3",
329420 ]
330421
422+[[package]]
423+name = "dotenvy"
424+version = "0.15.7"
425+source = "registry+https://github.com/rust-lang/crates.io-index"
426+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
427+
428+[[package]]
429+name = "either"
430+version = "1.17.0"
431+source = "registry+https://github.com/rust-lang/crates.io-index"
432+checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
433+dependencies = [
434+ "serde",
435+]
436+
331437 [[package]]
332438 name = "email-encoding"
333439 version = "0.4.2"
@@ -344,6 +450,15 @@ version = "0.2.9"
344450 source = "registry+https://github.com/rust-lang/crates.io-index"
345451 checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
346452
453+[[package]]
454+name = "envy"
455+version = "0.4.2"
456+source = "registry+https://github.com/rust-lang/crates.io-index"
457+checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
458+dependencies = [
459+ "serde",
460+]
461+
347462 [[package]]
348463 name = "equivalent"
349464 version = "1.0.2"
@@ -360,12 +475,38 @@ dependencies = [
360475 "windows-sys",
361476 ]
362477
478+[[package]]
479+name = "etcetera"
480+version = "0.11.0"
481+source = "registry+https://github.com/rust-lang/crates.io-index"
482+checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
483+dependencies = [
484+ "cfg-if",
485+ "windows-sys",
486+]
487+
488+[[package]]
489+name = "event-listener"
490+version = "5.4.2"
491+source = "registry+https://github.com/rust-lang/crates.io-index"
492+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
493+dependencies = [
494+ "parking",
495+ "pin-project-lite",
496+]
497+
363498 [[package]]
364499 name = "fastrand"
365500 version = "2.5.0"
366501 source = "registry+https://github.com/rust-lang/crates.io-index"
367502 checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
368503
504+[[package]]
505+name = "find-msvc-tools"
506+version = "0.1.9"
507+source = "registry+https://github.com/rust-lang/crates.io-index"
508+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
509+
369510 [[package]]
370511 name = "flate2"
371512 version = "1.1.9"
@@ -376,6 +517,17 @@ dependencies = [
376517 "miniz_oxide",
377518 ]
378519
520+[[package]]
521+name = "flume"
522+version = "0.12.0"
523+source = "registry+https://github.com/rust-lang/crates.io-index"
524+checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
525+dependencies = [
526+ "futures-core",
527+ "futures-sink",
528+ "spin",
529+]
530+
379531 [[package]]
380532 name = "fnv"
381533 version = "1.0.7"
@@ -404,6 +556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
404556 checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
405557 dependencies = [
406558 "futures-core",
559+ "futures-sink",
407560 ]
408561
409562 [[package]]
@@ -412,6 +565,34 @@ version = "0.3.33"
412565 source = "registry+https://github.com/rust-lang/crates.io-index"
413566 checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
414567
568+[[package]]
569+name = "futures-executor"
570+version = "0.3.33"
571+source = "registry+https://github.com/rust-lang/crates.io-index"
572+checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
573+dependencies = [
574+ "futures-core",
575+ "futures-task",
576+ "futures-util",
577+]
578+
579+[[package]]
580+name = "futures-intrusive"
581+version = "0.5.0"
582+source = "registry+https://github.com/rust-lang/crates.io-index"
583+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
584+dependencies = [
585+ "futures-core",
586+ "lock_api",
587+ "parking_lot",
588+]
589+
590+[[package]]
591+name = "futures-io"
592+version = "0.3.33"
593+source = "registry+https://github.com/rust-lang/crates.io-index"
594+checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
595+
415596 [[package]]
416597 name = "futures-macro"
417598 version = "0.3.33"
@@ -442,9 +623,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
442623 checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
443624 dependencies = [
444625 "futures-core",
626+ "futures-io",
445627 "futures-macro",
446628 "futures-sink",
447629 "futures-task",
630+ "memchr",
448631 "pin-project-lite",
449632 "slab",
450633 ]
@@ -494,6 +677,7 @@ dependencies = [
494677 "js-sys",
495678 "libc",
496679 "r-efi 6.0.0",
680+ "rand_core 0.10.1",
497681 "wasm-bindgen",
498682 ]
499683
@@ -526,6 +710,17 @@ dependencies = [
526710 "tracing",
527711 ]
528712
713+[[package]]
714+name = "hashbrown"
715+version = "0.16.1"
716+source = "registry+https://github.com/rust-lang/crates.io-index"
717+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
718+dependencies = [
719+ "allocator-api2",
720+ "equivalent",
721+ "foldhash",
722+]
723+
529724 [[package]]
530725 name = "hashbrown"
531726 version = "0.17.1"
@@ -537,19 +732,43 @@ dependencies = [
537732 "foldhash",
538733 ]
539734
735+[[package]]
736+name = "hashlink"
737+version = "0.11.1"
738+source = "registry+https://github.com/rust-lang/crates.io-index"
739+checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
740+dependencies = [
741+ "hashbrown 0.16.1",
742+]
743+
540744 [[package]]
541745 name = "heck"
542746 version = "0.5.0"
543747 source = "registry+https://github.com/rust-lang/crates.io-index"
544748 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
545749
750+[[package]]
751+name = "hex"
752+version = "0.4.3"
753+source = "registry+https://github.com/rust-lang/crates.io-index"
754+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
755+
546756 [[package]]
547757 name = "hkdf"
548758 version = "0.12.4"
549759 source = "registry+https://github.com/rust-lang/crates.io-index"
550760 checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
551761 dependencies = [
552 "hmac",
762+ "hmac 0.12.1",
763+]
764+
765+[[package]]
766+name = "hkdf"
767+version = "0.13.0"
768+source = "registry+https://github.com/rust-lang/crates.io-index"
769+checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
770+dependencies = [
771+ "hmac 0.13.0",
553772 ]
554773
555774 [[package]]
@@ -561,6 +780,15 @@ dependencies = [
561780 "digest 0.10.7",
562781 ]
563782
783+[[package]]
784+name = "hmac"
785+version = "0.13.0"
786+source = "registry+https://github.com/rust-lang/crates.io-index"
787+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
788+dependencies = [
789+ "digest 0.11.3",
790+]
791+
564792 [[package]]
565793 name = "hostname"
566794 version = "0.4.2"
@@ -771,7 +999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
771999 checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
7721000 dependencies = [
7731001 "equivalent",
774 "hashbrown",
1002+ "hashbrown 0.17.1",
7751003 ]
7761004
7771005 [[package]]
@@ -832,12 +1060,32 @@ version = "0.2.189"
8321060 source = "registry+https://github.com/rust-lang/crates.io-index"
8331061 checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
8341062
1063+[[package]]
1064+name = "libsqlite3-sys"
1065+version = "0.37.0"
1066+source = "registry+https://github.com/rust-lang/crates.io-index"
1067+checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
1068+dependencies = [
1069+ "cc",
1070+ "pkg-config",
1071+ "vcpkg",
1072+]
1073+
8351074 [[package]]
8361075 name = "litemap"
8371076 version = "0.8.2"
8381077 source = "registry+https://github.com/rust-lang/crates.io-index"
8391078 checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
8401079
1080+[[package]]
1081+name = "lock_api"
1082+version = "0.4.14"
1083+source = "registry+https://github.com/rust-lang/crates.io-index"
1084+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
1085+dependencies = [
1086+ "scopeguard",
1087+]
1088+
8411089 [[package]]
8421090 name = "log"
8431091 version = "0.4.33"
@@ -850,6 +1098,16 @@ version = "0.9.2"
8501098 source = "registry+https://github.com/rust-lang/crates.io-index"
8511099 checksum = "8863b587001c1b9a8a4e36008cebc6b3612cb1226fe2de94858e06092687b608"
8521100
1101+[[package]]
1102+name = "md-5"
1103+version = "0.11.0"
1104+source = "registry+https://github.com/rust-lang/crates.io-index"
1105+checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
1106+dependencies = [
1107+ "cfg-if",
1108+ "digest 0.11.3",
1109+]
1110+
8531111 [[package]]
8541112 name = "memchr"
8551113 version = "2.8.3"
@@ -898,6 +1156,15 @@ version = "0.2.2"
8981156 source = "registry+https://github.com/rust-lang/crates.io-index"
8991157 checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
9001158
1159+[[package]]
1160+name = "num-traits"
1161+version = "0.2.19"
1162+source = "registry+https://github.com/rust-lang/crates.io-index"
1163+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
1164+dependencies = [
1165+ "autocfg",
1166+]
1167+
9011168 [[package]]
9021169 name = "once_cell"
9031170 version = "1.21.4"
@@ -910,6 +1177,35 @@ version = "0.3.1"
9101177 source = "registry+https://github.com/rust-lang/crates.io-index"
9111178 checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
9121179
1180+[[package]]
1181+name = "parking"
1182+version = "2.2.1"
1183+source = "registry+https://github.com/rust-lang/crates.io-index"
1184+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
1185+
1186+[[package]]
1187+name = "parking_lot"
1188+version = "0.12.5"
1189+source = "registry+https://github.com/rust-lang/crates.io-index"
1190+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
1191+dependencies = [
1192+ "lock_api",
1193+ "parking_lot_core",
1194+]
1195+
1196+[[package]]
1197+name = "parking_lot_core"
1198+version = "0.9.12"
1199+source = "registry+https://github.com/rust-lang/crates.io-index"
1200+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
1201+dependencies = [
1202+ "cfg-if",
1203+ "libc",
1204+ "redox_syscall",
1205+ "smallvec",
1206+ "windows-link",
1207+]
1208+
9131209 [[package]]
9141210 name = "percent-encoding"
9151211 version = "2.3.2"
@@ -922,6 +1218,12 @@ version = "0.2.17"
9221218 source = "registry+https://github.com/rust-lang/crates.io-index"
9231219 checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
9241220
1221+[[package]]
1222+name = "pkg-config"
1223+version = "0.3.33"
1224+source = "registry+https://github.com/rust-lang/crates.io-index"
1225+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
1226+
9251227 [[package]]
9261228 name = "polyval"
9271229 version = "0.6.2"
@@ -1024,6 +1326,17 @@ dependencies = [
10241326 "rand_core 0.9.5",
10251327 ]
10261328
1329+[[package]]
1330+name = "rand"
1331+version = "0.10.2"
1332+source = "registry+https://github.com/rust-lang/crates.io-index"
1333+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
1334+dependencies = [
1335+ "chacha20",
1336+ "getrandom 0.4.3",
1337+ "rand_core 0.10.1",
1338+]
1339+
10271340 [[package]]
10281341 name = "rand_chacha"
10291342 version = "0.3.1"
@@ -1062,6 +1375,21 @@ dependencies = [
10621375 "getrandom 0.3.4",
10631376 ]
10641377
1378+[[package]]
1379+name = "rand_core"
1380+version = "0.10.1"
1381+source = "registry+https://github.com/rust-lang/crates.io-index"
1382+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
1383+
1384+[[package]]
1385+name = "redox_syscall"
1386+version = "0.5.18"
1387+source = "registry+https://github.com/rust-lang/crates.io-index"
1388+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
1389+dependencies = [
1390+ "bitflags",
1391+]
1392+
10651393 [[package]]
10661394 name = "ref-cast"
10671395 version = "1.0.26"
@@ -1094,6 +1422,12 @@ version = "1.0.23"
10941422 source = "registry+https://github.com/rust-lang/crates.io-index"
10951423 checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
10961424
1425+[[package]]
1426+name = "scopeguard"
1427+version = "1.2.0"
1428+source = "registry+https://github.com/rust-lang/crates.io-index"
1429+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1430+
10971431 [[package]]
10981432 name = "serde"
10991433 version = "1.0.229"
@@ -1180,6 +1514,17 @@ dependencies = [
11801514 "digest 0.10.7",
11811515 ]
11821516
1517+[[package]]
1518+name = "sha1"
1519+version = "0.11.0"
1520+source = "registry+https://github.com/rust-lang/crates.io-index"
1521+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
1522+dependencies = [
1523+ "cfg-if",
1524+ "cpufeatures 0.3.0",
1525+ "digest 0.11.3",
1526+]
1527+
11831528 [[package]]
11841529 name = "sha2"
11851530 version = "0.10.9"
@@ -1202,6 +1547,12 @@ dependencies = [
12021547 "digest 0.11.3",
12031548 ]
12041549
1550+[[package]]
1551+name = "shlex"
1552+version = "2.0.1"
1553+source = "registry+https://github.com/rust-lang/crates.io-index"
1554+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
1555+
12051556 [[package]]
12061557 name = "signal-hook-registry"
12071558 version = "1.4.8"
@@ -1229,6 +1580,9 @@ name = "smallvec"
12291580 version = "1.15.2"
12301581 source = "registry+https://github.com/rust-lang/crates.io-index"
12311582 checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
1583+dependencies = [
1584+ "serde",
1585+]
12321586
12331587 [[package]]
12341588 name = "socket2"
@@ -1240,6 +1594,182 @@ dependencies = [
12401594 "windows-sys",
12411595 ]
12421596
1597+[[package]]
1598+name = "spin"
1599+version = "0.9.9"
1600+source = "registry+https://github.com/rust-lang/crates.io-index"
1601+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
1602+dependencies = [
1603+ "lock_api",
1604+]
1605+
1606+[[package]]
1607+name = "sqlx"
1608+version = "0.9.0"
1609+source = "registry+https://github.com/rust-lang/crates.io-index"
1610+checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71"
1611+dependencies = [
1612+ "sqlx-core",
1613+ "sqlx-macros",
1614+ "sqlx-mysql",
1615+ "sqlx-postgres",
1616+ "sqlx-sqlite",
1617+]
1618+
1619+[[package]]
1620+name = "sqlx-core"
1621+version = "0.9.0"
1622+source = "registry+https://github.com/rust-lang/crates.io-index"
1623+checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb"
1624+dependencies = [
1625+ "base64 0.22.1",
1626+ "bytes",
1627+ "cfg-if",
1628+ "crc",
1629+ "crossbeam-queue",
1630+ "either",
1631+ "event-listener",
1632+ "futures-core",
1633+ "futures-intrusive",
1634+ "futures-io",
1635+ "futures-util",
1636+ "hashbrown 0.16.1",
1637+ "hashlink",
1638+ "indexmap",
1639+ "log",
1640+ "memchr",
1641+ "percent-encoding",
1642+ "serde",
1643+ "serde_json",
1644+ "sha2 0.10.9",
1645+ "smallvec",
1646+ "thiserror",
1647+ "tokio",
1648+ "tokio-stream",
1649+ "tracing",
1650+ "url",
1651+]
1652+
1653+[[package]]
1654+name = "sqlx-macros"
1655+version = "0.9.0"
1656+source = "registry+https://github.com/rust-lang/crates.io-index"
1657+checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf"
1658+dependencies = [
1659+ "proc-macro2",
1660+ "quote",
1661+ "sqlx-core",
1662+ "sqlx-macros-core",
1663+ "syn 2.0.119",
1664+]
1665+
1666+[[package]]
1667+name = "sqlx-macros-core"
1668+version = "0.9.0"
1669+source = "registry+https://github.com/rust-lang/crates.io-index"
1670+checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3"
1671+dependencies = [
1672+ "cfg-if",
1673+ "dotenvy",
1674+ "either",
1675+ "heck",
1676+ "hex",
1677+ "proc-macro2",
1678+ "quote",
1679+ "serde",
1680+ "serde_json",
1681+ "sha2 0.10.9",
1682+ "sqlx-core",
1683+ "sqlx-sqlite",
1684+ "syn 2.0.119",
1685+ "thiserror",
1686+ "tokio",
1687+ "url",
1688+]
1689+
1690+[[package]]
1691+name = "sqlx-mysql"
1692+version = "0.9.0"
1693+source = "registry+https://github.com/rust-lang/crates.io-index"
1694+checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27"
1695+dependencies = [
1696+ "bitflags",
1697+ "byteorder",
1698+ "bytes",
1699+ "digest 0.11.3",
1700+ "dotenvy",
1701+ "either",
1702+ "futures-core",
1703+ "futures-util",
1704+ "generic-array",
1705+ "log",
1706+ "percent-encoding",
1707+ "serde",
1708+ "sha1 0.11.0",
1709+ "sha2 0.11.0",
1710+ "sqlx-core",
1711+ "thiserror",
1712+ "tracing",
1713+]
1714+
1715+[[package]]
1716+name = "sqlx-postgres"
1717+version = "0.9.0"
1718+source = "registry+https://github.com/rust-lang/crates.io-index"
1719+checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e"
1720+dependencies = [
1721+ "atoi",
1722+ "base64 0.22.1",
1723+ "bitflags",
1724+ "byteorder",
1725+ "dotenvy",
1726+ "etcetera",
1727+ "futures-channel",
1728+ "futures-core",
1729+ "futures-util",
1730+ "hex",
1731+ "hkdf 0.13.0",
1732+ "hmac 0.13.0",
1733+ "itoa",
1734+ "log",
1735+ "md-5",
1736+ "memchr",
1737+ "rand 0.10.2",
1738+ "serde",
1739+ "serde_json",
1740+ "sha2 0.11.0",
1741+ "smallvec",
1742+ "sqlx-core",
1743+ "stringprep",
1744+ "thiserror",
1745+ "tracing",
1746+ "whoami",
1747+]
1748+
1749+[[package]]
1750+name = "sqlx-sqlite"
1751+version = "0.9.0"
1752+source = "registry+https://github.com/rust-lang/crates.io-index"
1753+checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
1754+dependencies = [
1755+ "atoi",
1756+ "flume",
1757+ "form_urlencoded",
1758+ "futures-channel",
1759+ "futures-core",
1760+ "futures-executor",
1761+ "futures-intrusive",
1762+ "futures-util",
1763+ "libsqlite3-sys",
1764+ "log",
1765+ "percent-encoding",
1766+ "serde",
1767+ "sqlx-core",
1768+ "thiserror",
1769+ "tracing",
1770+ "url",
1771+]
1772+
12431773 [[package]]
12441774 name = "stable_deref_trait"
12451775 version = "1.2.1"
@@ -1250,10 +1780,25 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
12501780 name = "steid"
12511781 version = "0.1.0"
12521782 dependencies = [
1783+ "dotenvy",
1784+ "envy",
1785+ "serde",
1786+ "sqlx",
12531787 "tokio",
12541788 "topcoat",
12551789 ]
12561790
1791+[[package]]
1792+name = "stringprep"
1793+version = "0.1.5"
1794+source = "registry+https://github.com/rust-lang/crates.io-index"
1795+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
1796+dependencies = [
1797+ "unicode-bidi",
1798+ "unicode-normalization",
1799+ "unicode-properties",
1800+]
1801+
12571802 [[package]]
12581803 name = "subtle"
12591804 version = "2.6.1"
@@ -1359,6 +1904,21 @@ dependencies = [
13591904 "zerovec",
13601905 ]
13611906
1907+[[package]]
1908+name = "tinyvec"
1909+version = "1.12.0"
1910+source = "registry+https://github.com/rust-lang/crates.io-index"
1911+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
1912+dependencies = [
1913+ "tinyvec_macros",
1914+]
1915+
1916+[[package]]
1917+name = "tinyvec_macros"
1918+version = "0.1.1"
1919+source = "registry+https://github.com/rust-lang/crates.io-index"
1920+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
1921+
13621922 [[package]]
13631923 name = "tokio"
13641924 version = "1.53.1"
@@ -1386,6 +1946,17 @@ dependencies = [
13861946 "syn 3.0.3",
13871947 ]
13881948
1949+[[package]]
1950+name = "tokio-stream"
1951+version = "0.1.19"
1952+source = "registry+https://github.com/rust-lang/crates.io-index"
1953+checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
1954+dependencies = [
1955+ "futures-core",
1956+ "pin-project-lite",
1957+ "tokio",
1958+]
1959+
13891960 [[package]]
13901961 name = "tokio-tungstenite"
13911962 version = "0.29.0"
@@ -1535,7 +2106,7 @@ dependencies = [
15352106 "anyhow",
15362107 "anymap3",
15372108 "boxcar",
1538 "hashbrown",
2109+ "hashbrown 0.17.1",
15392110 "http",
15402111 "pin-project-lite",
15412112 "thiserror",
@@ -1863,10 +2434,23 @@ version = "0.1.44"
18632434 source = "registry+https://github.com/rust-lang/crates.io-index"
18642435 checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
18652436 dependencies = [
2437+ "log",
18662438 "pin-project-lite",
2439+ "tracing-attributes",
18672440 "tracing-core",
18682441 ]
18692442
2443+[[package]]
2444+name = "tracing-attributes"
2445+version = "0.1.31"
2446+source = "registry+https://github.com/rust-lang/crates.io-index"
2447+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
2448+dependencies = [
2449+ "proc-macro2",
2450+ "quote",
2451+ "syn 2.0.119",
2452+]
2453+
18702454 [[package]]
18712455 name = "tracing-core"
18722456 version = "0.1.36"
@@ -1888,7 +2472,7 @@ dependencies = [
18882472 "httparse",
18892473 "log",
18902474 "rand 0.9.5",
1891 "sha1",
2475+ "sha1 0.10.7",
18922476 "thiserror",
18932477 ]
18942478
@@ -1898,12 +2482,33 @@ version = "1.20.1"
18982482 source = "registry+https://github.com/rust-lang/crates.io-index"
18992483 checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
19002484
2485+[[package]]
2486+name = "unicode-bidi"
2487+version = "0.3.18"
2488+source = "registry+https://github.com/rust-lang/crates.io-index"
2489+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
2490+
19012491 [[package]]
19022492 name = "unicode-ident"
19032493 version = "1.0.24"
19042494 source = "registry+https://github.com/rust-lang/crates.io-index"
19052495 checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
19062496
2497+[[package]]
2498+name = "unicode-normalization"
2499+version = "0.1.25"
2500+source = "registry+https://github.com/rust-lang/crates.io-index"
2501+checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
2502+dependencies = [
2503+ "tinyvec",
2504+]
2505+
2506+[[package]]
2507+name = "unicode-properties"
2508+version = "0.1.4"
2509+source = "registry+https://github.com/rust-lang/crates.io-index"
2510+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
2511+
19072512 [[package]]
19082513 name = "universal-hash"
19092514 version = "0.5.1"
@@ -1914,6 +2519,18 @@ dependencies = [
19142519 "subtle",
19152520 ]
19162521
2522+[[package]]
2523+name = "url"
2524+version = "2.5.8"
2525+source = "registry+https://github.com/rust-lang/crates.io-index"
2526+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
2527+dependencies = [
2528+ "form_urlencoded",
2529+ "idna",
2530+ "percent-encoding",
2531+ "serde",
2532+]
2533+
19172534 [[package]]
19182535 name = "utf8_iter"
19192536 version = "1.0.4"
@@ -1932,6 +2549,12 @@ dependencies = [
19322549 "wasm-bindgen",
19332550 ]
19342551
2552+[[package]]
2553+name = "vcpkg"
2554+version = "0.2.15"
2555+source = "registry+https://github.com/rust-lang/crates.io-index"
2556+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
2557+
19352558 [[package]]
19362559 name = "version_check"
19372560 version = "0.9.5"
@@ -2008,6 +2631,12 @@ dependencies = [
20082631 "wasm-bindgen",
20092632 ]
20102633
2634+[[package]]
2635+name = "whoami"
2636+version = "2.1.2"
2637+source = "registry+https://github.com/rust-lang/crates.io-index"
2638+checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d"
2639+
20112640 [[package]]
20122641 name = "windows-link"

Showing the first 1000 lines of 1001. View the whole file at this revision.

Cargo.toml+4 −0View file
@@ -4,5 +4,9 @@ version = "0.1.0"
44 edition = "2024"
55
66 [dependencies]
7+dotenvy = "0.15.7"
8+envy = "0.4.2"
9+serde = { version = "1.0.229", features = ["derive"] }
10+sqlx = { version = "0.9.0", default-features = false, features = ["runtime-tokio", "sqlite", "macros"] }
711 tokio = { version = "1.53.1", features = ["rt-multi-thread", "macros"] }
812 topcoat = "0.5.0"
src/application/config.rs+76 −0View file
@@ -0,0 +1,76 @@
1+use std::path::PathBuf;
2+
3+use serde::Deserialize;
4+
5+/// Application configuration, read from `STEID_`-prefixed environment variables.
6+///
7+/// The bind address is deliberately absent: Topcoat owns it via `HOST` and `PORT`.
8+#[derive(Debug, Clone, Deserialize)]
9+pub struct AppConfig {
10+ /// SQLite connection string. Needs `mode=rwc` to create the file on first boot.
11+ #[serde(default = "default_database_url")]
12+ pub database_url: String,
13+
14+ /// Root directory for bare git repositories, laid out as `{data_dir}/{org}/{repo}.git`.
15+ ///
16+ /// Unread until Milestone 2 introduces `GitStorage`; carried now so the config
17+ /// surface matches the documented environment.
18+ #[allow(dead_code)]
19+ #[serde(default = "default_data_dir")]
20+ pub data_dir: PathBuf,
21+}
22+
23+fn default_database_url() -> String {
24+ "sqlite:steid.db?mode=rwc".to_owned()
25+}
26+
27+fn default_data_dir() -> PathBuf {
28+ PathBuf::from("./data")
29+}
30+
31+impl AppConfig {
32+ /// Loads configuration from the environment, applying defaults for anything unset.
33+ pub fn from_env() -> Result<Self, envy::Error> {
34+ envy::prefixed("STEID_").from_env()
35+ }
36+}
37+
38+#[cfg(test)]
39+mod tests {
40+ use super::*;
41+
42+ /// Builds a config from an explicit iterator rather than the process environment,
43+ /// so tests don't race on shared global state.
44+ fn from_pairs(pairs: &[(&str, &str)]) -> Result<AppConfig, envy::Error> {
45+ envy::prefixed("STEID_")
46+ .from_iter(pairs.iter().map(|(k, v)| ((*k).to_owned(), (*v).to_owned())))
47+ }
48+
49+ #[test]
50+ fn applies_defaults_when_environment_is_empty() {
51+ let config = from_pairs(&[]).expect("defaults should satisfy every field");
52+
53+ assert_eq!(config.database_url, "sqlite:steid.db?mode=rwc");
54+ assert_eq!(config.data_dir, PathBuf::from("./data"));
55+ }
56+
57+ #[test]
58+ fn reads_prefixed_variables() {
59+ let config = from_pairs(&[
60+ ("STEID_DATABASE_URL", "sqlite:custom.db?mode=rwc"),
61+ ("STEID_DATA_DIR", "/srv/steid/repos"),
62+ ])
63+ .expect("explicit values should parse");
64+
65+ assert_eq!(config.database_url, "sqlite:custom.db?mode=rwc");
66+ assert_eq!(config.data_dir, PathBuf::from("/srv/steid/repos"));
67+ }
68+
69+ #[test]
70+ fn ignores_unprefixed_variables() {
71+ let config = from_pairs(&[("DATA_DIR", "/should/be/ignored")])
72+ .expect("unprefixed keys should not interfere");
73+
74+ assert_eq!(config.data_dir, PathBuf::from("./data"));
75+ }
76+}
src/application/mod.rs+3 −0View file
@@ -0,0 +1,3 @@
1+pub mod config;
2+
3+pub use config::AppConfig;
src/infrastructure/database.rs+10 −0View file
@@ -0,0 +1,10 @@
1+use sqlx::SqlitePool;
2+use sqlx::sqlite::SqlitePoolOptions;
3+
4+/// Opens the SQLite pool.
5+///
6+/// The pool is cheap to clone and shared by reference across every request, so it is
7+/// registered once as Topcoat app context rather than rebuilt per handler.
8+pub async fn connect(database_url: &str) -> Result<SqlitePool, sqlx::Error> {
9+ SqlitePoolOptions::new().connect(database_url).await
10+}
src/infrastructure/mod.rs+2 −0View file
@@ -0,0 +1,2 @@
1+pub mod database;
2+pub mod web;
src/infrastructure/web.rs+34 −0View file
@@ -0,0 +1,34 @@
1+use sqlx::SqlitePool;
2+use topcoat::{
3+ Result,
4+ context::{Cx, app_context},
5+ router::page,
6+ view::view,
7+};
8+
9+/// Placeholder home page.
10+///
11+/// Milestone 0 only: it renders a value read through the pool to prove config,
12+/// database, and app context are wired end to end. Milestone 1 replaces it with the
13+/// profile page, which is the real home page.
14+#[page("/")]
15+async fn home(cx: &Cx) -> Result {
16+ let pool: &SqlitePool = app_context(cx);
17+ let sqlite_version: String = sqlx::query_scalar("select sqlite_version()")
18+ .fetch_one(pool)
19+ .await?;
20+
21+ view! {
22+ <!DOCTYPE html>
23+ <html>
24+ <head>
25+ <title>"steid"</title>
26+ topcoat::dev::script()
27+ </head>
28+ <body>
29+ <h1>"steid"</h1>
30+ <p>"sqlite " (sqlite_version)</p>
31+ </body>
32+ </html>
33+ }
34+}
src/main.rs+18 −30View file
@@ -1,35 +1,23 @@
1use topcoat::{
2 Result,
3 router::{Router, RouterBuilderDiscoverExt, page},
4 view::{component, view},
5};
1+mod application;
2+mod infrastructure;
3+
4+use application::AppConfig;
5+use topcoat::router::{Router, RouterBuilderDiscoverExt};
66
77 #[tokio::main]
8async fn main() {
9 topcoat::start(Router::builder().discover().build())
10 .await
11 .unwrap();
12}
8+async fn main() -> Result<(), Box<dyn std::error::Error>> {
9+ dotenvy::dotenv().ok();
1310
14#[page("/")]
15async fn home() -> Result {
16 view! {
17 <!DOCTYPE html>
18 <html>
19 <head>
20 <title>"steid"</title>
21 topcoat::dev::script()
22 </head>
23 <body>
24 heading(name: "steid")
25 </body>
26 </html>
27 }
28}
11+ let config = AppConfig::from_env()?;
12+ let pool = infrastructure::database::connect(&config.database_url).await?;
13+
14+ let router = Router::builder()
15+ .discover()
16+ .app_context(config)
17+ .app_context(pool)
18+ .build();
19+
20+ topcoat::start(router).await?;
2921
30#[component]
31async fn heading(name: &str) -> Result {
32 view! {
33 <h1>(name)</h1>
34 }
22+ Ok(())
3523 }