simplistic form rendering

This commit is contained in:
Brent Schroeter 2025-10-01 22:37:11 -07:00
parent 2ca7554a09
commit febace2eab
15 changed files with 615 additions and 55 deletions

397
Cargo.lock generated
View file

@ -23,7 +23,7 @@ version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"once_cell",
"version_check",
"zerocopy",
@ -356,7 +356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"libc",
"miniz_oxide",
"object",
@ -414,9 +414,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.0"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
dependencies = [
"serde",
]
@ -489,9 +489,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "chrono"
@ -690,7 +690,7 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
]
[[package]]
@ -744,6 +744,29 @@ dependencies = [
"subtle",
]
[[package]]
name = "cssparser"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa"
dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf",
"smallvec",
]
[[package]]
name = "cssparser-macros"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "darling"
version = "0.20.11"
@ -836,6 +859,26 @@ dependencies = [
"syn",
]
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.9.0"
@ -883,6 +926,27 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dtoa"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04"
[[package]]
name = "dtoa-short"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
dependencies = [
"dtoa",
]
[[package]]
name = "ego-tree"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8"
[[package]]
name = "either"
version = "1.15.0"
@ -898,7 +962,7 @@ version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
]
[[package]]
@ -923,7 +987,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"home",
"windows-sys 0.48.0",
]
@ -1008,6 +1072,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
dependencies = [
"mac",
"new_debug_unreachable",
]
[[package]]
name = "futures"
version = "0.3.31"
@ -1108,6 +1182,15 @@ dependencies = [
"slab",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@ -1118,13 +1201,22 @@ dependencies = [
"version_check",
]
[[package]]
name = "getopts"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
@ -1137,7 +1229,7 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
@ -1199,9 +1291,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.3"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
@ -1223,7 +1315,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.3",
"hashbrown 0.15.5",
]
[[package]]
@ -1299,6 +1391,17 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "html5ever"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4"
dependencies = [
"log",
"markup5ever",
"match_token",
]
[[package]]
name = "http"
version = "0.2.12"
@ -1629,7 +1732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown 0.15.3",
"hashbrown 0.15.5",
]
[[package]]
@ -1683,11 +1786,13 @@ dependencies = [
"headers",
"interim-models",
"interim-pgtypes",
"markdown",
"oauth2",
"percent-encoding",
"rand 0.8.5",
"regex",
"reqwest 0.12.15",
"scraper",
"serde",
"serde_json",
"sqlx",
@ -1800,6 +1905,43 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "mac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "markdown"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb"
dependencies = [
"unicode-id",
]
[[package]]
name = "markup5ever"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3"
dependencies = [
"log",
"tendril",
"web_atoms",
]
[[package]]
name = "match_token"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "matchers"
version = "0.1.0"
@ -1821,15 +1963,15 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"digest 0.10.7",
]
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "mime"
@ -1890,6 +2032,12 @@ dependencies = [
"tempfile",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nom"
version = "7.1.3"
@ -2019,8 +2167,8 @@ version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
dependencies = [
"bitflags 2.9.0",
"cfg-if 1.0.0",
"bitflags 2.9.4",
"cfg-if 1.0.3",
"foreign-types",
"libc",
"once_cell",
@ -2095,7 +2243,7 @@ version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"libc",
"redox_syscall",
"smallvec",
@ -2168,6 +2316,58 @@ dependencies = [
"sha2 0.10.9",
]
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand 0.8.5",
]
[[package]]
name = "phf_macros"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@ -2231,6 +2431,12 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
@ -2352,7 +2558,7 @@ version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags 2.9.0",
"bitflags 2.9.4",
]
[[package]]
@ -2491,7 +2697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"getrandom 0.2.16",
"libc",
"untrusted",
@ -2505,7 +2711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [
"base64 0.21.7",
"bitflags 2.9.0",
"bitflags 2.9.4",
"serde",
"serde_derive",
]
@ -2536,7 +2742,7 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"ordered-multimap",
]
@ -2558,7 +2764,7 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags 2.9.0",
"bitflags 2.9.4",
"errno",
"libc",
"linux-raw-sys",
@ -2678,6 +2884,21 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scraper"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5f3a24d916e78954af99281a455168d4a9515d65eca99a18da1b813689c4ad9"
dependencies = [
"cssparser",
"ego-tree",
"getopts",
"html5ever",
"precomputed-hash",
"selectors",
"tendril",
]
[[package]]
name = "sct"
version = "0.7.1"
@ -2694,7 +2915,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.9.0",
"bitflags 2.9.4",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
@ -2707,7 +2928,7 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
dependencies = [
"bitflags 2.9.0",
"bitflags 2.9.4",
"core-foundation 0.10.0",
"core-foundation-sys",
"libc",
@ -2724,6 +2945,25 @@ dependencies = [
"libc",
]
[[package]]
name = "selectors"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5685b6ae43bfcf7d2e7dfcfb5d8e8f61b46442c902531e41a32a9a8bf0ee0fb6"
dependencies = [
"bitflags 2.9.4",
"cssparser",
"derive_more",
"fxhash",
"log",
"new_debug_unreachable",
"phf",
"phf_codegen",
"precomputed-hash",
"servo_arc",
"smallvec",
]
[[package]]
name = "serde"
version = "1.0.219"
@ -2800,13 +3040,22 @@ dependencies = [
"serde",
]
[[package]]
name = "servo_arc"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4"
dependencies = [
"stable_deref_trait",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"cpufeatures",
"digest 0.10.7",
]
@ -2818,7 +3067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
@ -2830,7 +3079,7 @@ version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"cpufeatures",
"digest 0.10.7",
]
@ -2869,6 +3118,12 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.9"
@ -2946,7 +3201,7 @@ dependencies = [
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.15.3",
"hashbrown 0.15.5",
"hashlink 0.10.0",
"indexmap",
"log",
@ -3013,7 +3268,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.9.0",
"bitflags 2.9.4",
"byteorder",
"bytes",
"chrono",
@ -3057,7 +3312,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.9.0",
"bitflags 2.9.4",
"byteorder",
"chrono",
"crc",
@ -3120,6 +3375,31 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "string_cache"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
dependencies = [
"new_debug_unreachable",
"parking_lot",
"phf_shared",
"precomputed-hash",
"serde",
]
[[package]]
name = "string_cache_codegen"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
]
[[package]]
name = "stringprep"
version = "0.1.5"
@ -3218,7 +3498,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags 2.9.0",
"bitflags 2.9.4",
"core-foundation 0.9.4",
"system-configuration-sys 0.6.0",
]
@ -3256,6 +3536,17 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "tendril"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
dependencies = [
"futf",
"mac",
"utf-8",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@ -3302,7 +3593,7 @@ version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"once_cell",
]
@ -3530,7 +3821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
dependencies = [
"async-compression",
"bitflags 2.9.0",
"bitflags 2.9.4",
"bytes",
"futures-core",
"futures-util",
@ -3672,6 +3963,12 @@ version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-id"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580"
[[package]]
name = "unicode-ident"
version = "1.0.18"
@ -3699,6 +3996,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "untrusted"
version = "0.9.0"
@ -3829,7 +4132,7 @@ version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
@ -3855,7 +4158,7 @@ version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"js-sys",
"once_cell",
"wasm-bindgen",
@ -3904,6 +4207,18 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web_atoms"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414"
dependencies = [
"phf",
"phf_codegen",
"string_cache",
"string_cache_codegen",
]
[[package]]
name = "webpki-roots"
version = "0.25.4"
@ -4248,7 +4563,7 @@ version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if 1.0.0",
"cfg-if 1.0.3",
"windows-sys 0.48.0",
]
@ -4258,7 +4573,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.0",
"bitflags 2.9.4",
]
[[package]]

View file

@ -60,6 +60,7 @@ create table if not exists portals (
name text not null,
workspace_id uuid not null references workspaces(id) on delete cascade,
class_oid oid not null,
form_public boolean not null default false,
table_filter jsonb not null default 'null',
table_order_by jsonb not null default '[]'
);

View file

@ -28,6 +28,10 @@ pub struct Portal {
/// to be a normal table, not a view, etc.
pub class_oid: Oid,
/// Whether the portal's form may be viewed or submitted by users without
/// direct access to the portal itself.
pub form_public: bool,
/// JSONB-encoded expression to use for filtering rows in the web-based
/// table view.
pub table_filter: Json<Option<PgExpressionAny>>,
@ -73,6 +77,7 @@ select
name,
workspace_id,
class_oid,
form_public,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
from portals
where id = $1
@ -92,6 +97,7 @@ select
name,
workspace_id,
class_oid,
form_public,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
from portals
where id = $1
@ -118,6 +124,7 @@ select
name,
workspace_id,
class_oid,
form_public,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
from portals
where workspace_id = $1
@ -152,6 +159,7 @@ select
name,
workspace_id,
class_oid,
form_public,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
from portals
where workspace_id = $1 and class_oid = $2
@ -190,6 +198,7 @@ returning
name,
workspace_id,
class_oid,
form_public,
table_filter as "table_filter: Json<Option<PgExpressionAny>>"
"#,
self.workspace_id,
@ -204,8 +213,12 @@ returning
#[derive(Builder, Clone, Debug, Validate)]
pub struct Update {
id: Uuid,
#[builder(default, setter(strip_option = true))]
filter: Option<Option<PgExpressionAny>>,
form_public: Option<bool>,
#[builder(default, setter(strip_option = true))]
table_filter: Option<Option<PgExpressionAny>>,
#[builder(default, setter(strip_option = true))]
#[validate(regex(path = *RE_PORTAL_NAME))]
@ -217,18 +230,27 @@ impl Update {
self.validate()?;
// TODO: consolidate queries
if let Some(filter) = self.filter {
if let Some(form_public) = self.form_public {
query!(
"update portals set table_filter = $1 where id = $2",
Json(filter) as Json<Option<PgExpressionAny>>,
"update portals set form_public = $1 where id = $2",
form_public,
self.id
)
.execute(&mut *app_db.conn)
.execute(app_db.get_conn())
.await?;
}
if let Some(table_filter) = self.table_filter {
query!(
"update portals set table_filter = $1 where id = $2",
Json(table_filter) as Json<Option<PgExpressionAny>>,
self.id
)
.execute(app_db.get_conn())
.await?;
}
if let Some(name) = self.name {
query!("update portals set name = $1 where id = $2", name, self.id)
.execute(&mut *app_db.conn)
.execute(app_db.get_conn())
.await?;
}
Ok(())

View file

@ -18,11 +18,13 @@ futures = { workspace = true }
headers = "0.4.1"
interim-models = { workspace = true }
interim-pgtypes = { workspace = true }
markdown = "1.0.0"
oauth2 = "4.4.2"
percent-encoding = "2.3.1"
rand = { workspace = true }
regex = { workspace = true }
reqwest = { workspace = true }
scraper = "0.24.0"
serde = { workspace = true }
serde_json = { workspace = true}
sqlx = { workspace = true }

View file

@ -44,6 +44,13 @@ impl Navigator {
}
}
pub(crate) fn form_page(&self, portal_id: Uuid) -> FormPageBuilder {
FormPageBuilder {
root_path: Some(self.get_root_path()),
portal_id: Some(portal_id),
}
}
/// Returns a [`NavigatorPage`] builder for navigating to a relation's
/// "settings" page.
pub(crate) fn rel_settings_page(&self) -> RelSettingsPageBuilder {
@ -108,6 +115,24 @@ impl NavigatorPage for PortalPage {
}
}
#[derive(Builder, Clone, Debug)]
pub(crate) struct FormPage {
portal_id: Uuid,
#[builder(setter(custom))]
root_path: String,
}
impl NavigatorPage for FormPage {
fn get_path(&self) -> String {
format!(
"{root_path}/f/{portal_id}",
root_path = self.root_path,
portal_id = self.portal_id
)
}
}
#[derive(Builder, Clone, Debug)]
pub(crate) struct RelSettingsPage {
rel_oid: Oid,

View file

@ -0,0 +1,121 @@
use std::collections::HashMap;
use askama::Template;
use axum::{
extract::{Path, State},
response::{Html, IntoResponse as _, Response},
};
use interim_models::{
field::Field,
field_form_prompt::FieldFormPrompt,
language::Language,
portal::Portal,
presentation::{Presentation, TextInputMode},
};
use interim_pgtypes::{pg_attribute::PgAttribute, pg_class::PgClass};
use serde::Deserialize;
use uuid::Uuid;
use crate::{
Settings,
app::AppDbConn,
errors::{AppError, not_found},
field_info::FormFieldInfo,
workspace_pooler::{RoleAssignment, WorkspacePooler},
};
#[derive(Debug, Deserialize)]
pub(super) struct PathParams {
portal_id: Uuid,
}
pub(super) async fn get(
State(settings): State<Settings>,
State(mut pooler): State<WorkspacePooler>,
AppDbConn(mut app_db): AppDbConn,
Path(PathParams { portal_id }): Path<PathParams>,
) -> Result<Response, AppError> {
let portal = Portal::with_id(portal_id)
.fetch_optional(&mut app_db)
.await?
.ok_or(not_found!("form not found"))?;
// FIXME: auth
// WARNING: This client is connected with full workspace privileges. Even
// more so than usual, the Phonograph server is responsible for ensuring all
// auth checks are performed properly.
//
// TODO: Can this be delegated to a dedicated and less privileged role
// instead?
let mut workspace_client = pooler
.acquire_for(portal.workspace_id, RoleAssignment::Root)
.await?;
let rel = PgClass::with_oid(portal.class_oid)
.fetch_one(&mut workspace_client)
.await?;
let attrs: HashMap<String, PgAttribute> = PgAttribute::all_for_rel(portal.class_oid)
.fetch_all(&mut workspace_client)
.await?
.into_iter()
.map(|value| (value.attname.clone(), value))
.collect();
// TODO: implement with sql join
let mut fields: Vec<FormFieldInfo> = vec![];
for field in Field::belonging_to_portal(portal_id)
.fetch_all(&mut app_db)
.await?
{
let attr = attrs.get(&field.name);
let prompts: HashMap<Language, String> = FieldFormPrompt::belonging_to_field(field.id)
.fetch_all(&mut app_db)
.await?
.into_iter()
.map(|prompt| (prompt.language, prompt.content))
.collect();
fields.push(FormFieldInfo {
field,
column_present: attr.is_some(),
has_default: attr.is_some_and(|value| value.atthasdef),
not_null: attr.is_some_and(|value| value.attnotnull.is_some_and(|notnull| notnull)),
prompts,
})
}
let mut prompts_html: HashMap<String, String> = HashMap::new();
for field in fields.iter() {
// TODO: i18n
let prompt = field
.prompts
.get(&Language::Eng)
.cloned()
.unwrap_or_default();
let prompt_md = markdown::to_html(&prompt);
// TODO: a11y (input labels)
prompts_html.insert(field.field.name.clone(), prompt_md);
}
#[derive(Debug, Template)]
#[template(path = "forms/form_index.html")]
struct ResponseTemplate {
fields: Vec<FormFieldInfo>,
language: Language,
portal: Portal,
prompts_html: HashMap<String, String>,
settings: Settings,
}
Ok(Html(
ResponseTemplate {
fields,
language: Language::Eng,
portal,
prompts_html,
settings,
}
.render()?,
)
.into_response())
}

View file

@ -0,0 +1,10 @@
use axum::{Router, routing::get};
use axum_extra::routing::RouterExt as _;
use crate::app::App;
mod form_handler;
pub(super) fn new_router() -> Router<App> {
Router::new().route_with_tsr("/{portal_id}/", get(form_handler::get))
}

View file

@ -22,6 +22,7 @@ use tower_http::{
use crate::auth;
use crate::{app::App, settings::Settings};
mod forms;
mod relations_single;
mod workspaces_multi;
mod workspaces_single;
@ -42,6 +43,7 @@ pub(crate) fn new_router(app: App) -> Router<()> {
)
.nest("/workspaces", workspaces_multi::new_router())
.nest("/w", workspaces_single::new_router())
.nest("/f", forms::new_router())
.nest("/auth", auth::new_router())
.route("/__dev-healthz", get(|| async move { "ok" }))
.layer(SetResponseHeaderLayer::if_not_present(

View file

@ -15,7 +15,7 @@ use interim_models::{
workspace::Workspace,
workspace_user_perm::{self, WorkspaceUserPerm},
};
use interim_pgtypes::{pg_attribute::PgAttribute, pg_class::PgClass};
use interim_pgtypes::pg_attribute::PgAttribute;
use serde::{Deserialize, Serialize};
use sqlx::postgres::types::Oid;
use strum::IntoEnumIterator as _;
@ -25,7 +25,7 @@ use crate::{
app::{App, AppDbConn},
errors::{AppError, forbidden},
field_info::FormFieldInfo,
navigator::Navigator,
navigator::{Navigator, NavigatorPage as _},
settings::Settings,
user::CurrentUser,
workspace_nav::{NavLocation, RelLocation, WorkspaceNav},
@ -116,6 +116,8 @@ pub(super) async fn get(
fields: Vec<FormFieldInfo>,
identifier_hints: Vec<String>,
languages: Vec<LanguageInfo>,
navigator: Navigator,
portal: Portal,
portals: Vec<PortalDisplay>,
settings: Settings,
transitions: Vec<FormTransition>,
@ -141,6 +143,7 @@ pub(super) async fn get(
locale_str: value.as_locale_str().to_owned(),
})
.collect(),
portal,
portals: portal_sets
.iter()
.flat_map(|RelationPortalSet { rel, portals }| {
@ -158,7 +161,7 @@ pub(super) async fn get(
.fetch_all(&mut app_db)
.await?,
workspace_nav: WorkspaceNav::builder()
.navigator(navigator)
.navigator(navigator.clone())
.workspace(workspace)
.populate_rels(&mut app_db, &mut workspace_client)
.await?
@ -167,6 +170,7 @@ pub(super) async fn get(
Some(RelLocation::Portal(portal_id)),
))
.build()?,
navigator,
settings,
}
.render()?,

View file

@ -64,7 +64,7 @@ pub(super) async fn post(
serde_json::from_str(&form.filter_expression.unwrap_or("null".to_owned()))?;
Portal::update()
.id(portal.id)
.filter(filter)
.table_filter(filter)
.build()?
.execute(&mut app_db)
.await?;

View file

@ -1,16 +1,17 @@
<!doctype html>
<html lang="en" data-bs-theme="dark">
<html lang="en">
<head>
<title>{% block title %}Interim{% endblock %}</title>
{% include "meta_tags.html" %}
<link rel="stylesheet" href="{{ settings.root_path }}/css_dist/main.css">
{%- block head_extras %}{% endblock -%}
</head>
<body>
{% block main %}{% endblock main %}
{% block main %}{% endblock %}
{% if settings.dev != 0 %}
<script type="module">
import { initDevReloader } from "{{ settings.root_path }}/dev_reloader.mjs";
initDevReloader("http://127.0.0.1:8080{{ settings.root_path }}/__dev-healthz");
initDevReloader("{{ settings.frontend_host }}{{ settings.root_path }}/__dev-healthz");
</script>
{% endif %}
</body>

View file

@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block title %}{{ portal.name }}{% endblock %}
{% block head_extras %}
<link rel="stylesheet" href="{{ settings.root_path }}/css_dist/form.css">
{% endblock %}
{% block main %}
<main class="phono-form__container">
<form action="submit" method="post">
<section>
{% for field in fields %}
<div class="phono-form__field">
{% if field.column_present %}
{{ prompts_html.get(field.field.name).cloned().unwrap_or_default() | safe }}
{% match field.field.presentation.0 %}
{% when Presentation::Text { input_mode } %}
{% match input_mode %}
{% when TextInputMode::SingleLine %}
<input
type="text"
class="form-section__input form-section__input--text"
name="{{ field.field.name }}"
>
{% when TextInputMode::MultiLine %}
<textarea name="{{ field.field.name }}"></textarea>
{% else %}
{% endmatch %}
{% else %}
{% endmatch %}
{% endif %}
</div>
{% endfor %}
</section>
<section>
<button class="button--primary" type="submit">
Continue
</button>
</section>
</form>
</main>
{% endblock %}

View file

@ -3,6 +3,11 @@
{% block main %}
<div class="page-grid">
<div class="page-grid__toolbar">
<div class="page-grid__toolbar-utilities">
<a href="{{ navigator.form_page(*portal.id).build()?.get_path() }}">
<button class="button--secondary" style="margin-left: 0.5rem;" type="button">View Form</button>
</a>
</div>
</div>
<div class="page-grid__sidebar">
<div style="padding: 1rem;">

10
sass/form.scss Normal file
View file

@ -0,0 +1,10 @@
@use 'globals';
.phono-form{
&__container {
margin: 0 auto;
max-width: 48rem;
padding: 2rem;
position: relative;
}
}

View file

@ -81,7 +81,6 @@ button, input[type="submit"] {
&__toolbar-utilities {
align-items: center;
border-bottom: globals.$default-border;
display: flex;
grid-area: utilities;
justify-content: flex-start;