From febace2eab9a8ad92f63ed6360612035f2b37ed0 Mon Sep 17 00:00:00 2001 From: Brent Schroeter Date: Wed, 1 Oct 2025 22:37:11 -0700 Subject: [PATCH] simplistic form rendering --- Cargo.lock | 397 ++++++++++++++++-- .../migrations/20250918060948_init.up.sql | 1 + interim-models/src/portal.rs | 34 +- interim-server/Cargo.toml | 2 + interim-server/src/navigator.rs | 25 ++ .../src/routes/forms/form_handler.rs | 121 ++++++ interim-server/src/routes/forms/mod.rs | 10 + interim-server/src/routes/mod.rs | 2 + .../routes/relations_single/form_handler.rs | 10 +- .../relations_single/set_filter_handler.rs | 2 +- interim-server/templates/base.html | 7 +- .../templates/forms/form_index.html | 43 ++ .../relations_single/form_index.html | 5 + sass/form.scss | 10 + sass/main.scss | 1 - 15 files changed, 615 insertions(+), 55 deletions(-) create mode 100644 interim-server/src/routes/forms/form_handler.rs create mode 100644 interim-server/src/routes/forms/mod.rs create mode 100644 interim-server/templates/forms/form_index.html create mode 100644 sass/form.scss diff --git a/Cargo.lock b/Cargo.lock index 6e48f45..5162b44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] diff --git a/interim-models/migrations/20250918060948_init.up.sql b/interim-models/migrations/20250918060948_init.up.sql index 80dc9a1..5c2ce18 100644 --- a/interim-models/migrations/20250918060948_init.up.sql +++ b/interim-models/migrations/20250918060948_init.up.sql @@ -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 '[]' ); diff --git a/interim-models/src/portal.rs b/interim-models/src/portal.rs index 9507e72..6621cd1 100644 --- a/interim-models/src/portal.rs +++ b/interim-models/src/portal.rs @@ -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>, @@ -73,6 +77,7 @@ select name, workspace_id, class_oid, + form_public, table_filter as "table_filter: Json>" from portals where id = $1 @@ -92,6 +97,7 @@ select name, workspace_id, class_oid, + form_public, table_filter as "table_filter: Json>" from portals where id = $1 @@ -118,6 +124,7 @@ select name, workspace_id, class_oid, + form_public, table_filter as "table_filter: Json>" from portals where workspace_id = $1 @@ -152,6 +159,7 @@ select name, workspace_id, class_oid, + form_public, table_filter as "table_filter: Json>" 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>" "#, 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>, + form_public: Option, + + #[builder(default, setter(strip_option = true))] + table_filter: Option>, #[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>, + "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>, + 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(()) diff --git a/interim-server/Cargo.toml b/interim-server/Cargo.toml index 4763d02..0e6cac0 100644 --- a/interim-server/Cargo.toml +++ b/interim-server/Cargo.toml @@ -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 } diff --git a/interim-server/src/navigator.rs b/interim-server/src/navigator.rs index 1c96c9e..39cbca4 100644 --- a/interim-server/src/navigator.rs +++ b/interim-server/src/navigator.rs @@ -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, diff --git a/interim-server/src/routes/forms/form_handler.rs b/interim-server/src/routes/forms/form_handler.rs new file mode 100644 index 0000000..0c12a26 --- /dev/null +++ b/interim-server/src/routes/forms/form_handler.rs @@ -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, + State(mut pooler): State, + AppDbConn(mut app_db): AppDbConn, + Path(PathParams { portal_id }): Path, +) -> Result { + 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 = 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 = 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 = 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 = 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, + language: Language, + portal: Portal, + prompts_html: HashMap, + settings: Settings, + } + + Ok(Html( + ResponseTemplate { + fields, + language: Language::Eng, + portal, + prompts_html, + settings, + } + .render()?, + ) + .into_response()) +} diff --git a/interim-server/src/routes/forms/mod.rs b/interim-server/src/routes/forms/mod.rs new file mode 100644 index 0000000..ffd8c8d --- /dev/null +++ b/interim-server/src/routes/forms/mod.rs @@ -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 { + Router::new().route_with_tsr("/{portal_id}/", get(form_handler::get)) +} diff --git a/interim-server/src/routes/mod.rs b/interim-server/src/routes/mod.rs index 62b539f..73037e6 100644 --- a/interim-server/src/routes/mod.rs +++ b/interim-server/src/routes/mod.rs @@ -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( diff --git a/interim-server/src/routes/relations_single/form_handler.rs b/interim-server/src/routes/relations_single/form_handler.rs index bf37274..4d82335 100644 --- a/interim-server/src/routes/relations_single/form_handler.rs +++ b/interim-server/src/routes/relations_single/form_handler.rs @@ -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, identifier_hints: Vec, languages: Vec, + navigator: Navigator, + portal: Portal, portals: Vec, settings: Settings, transitions: Vec, @@ -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()?, diff --git a/interim-server/src/routes/relations_single/set_filter_handler.rs b/interim-server/src/routes/relations_single/set_filter_handler.rs index afbe9d0..aafb1a5 100644 --- a/interim-server/src/routes/relations_single/set_filter_handler.rs +++ b/interim-server/src/routes/relations_single/set_filter_handler.rs @@ -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?; diff --git a/interim-server/templates/base.html b/interim-server/templates/base.html index b4adabe..51c8f74 100644 --- a/interim-server/templates/base.html +++ b/interim-server/templates/base.html @@ -1,16 +1,17 @@ - + {% block title %}Interim{% endblock %} {% include "meta_tags.html" %} + {%- block head_extras %}{% endblock -%} - {% block main %}{% endblock main %} + {% block main %}{% endblock %} {% if settings.dev != 0 %} {% endif %} diff --git a/interim-server/templates/forms/form_index.html b/interim-server/templates/forms/form_index.html new file mode 100644 index 0000000..29f1fdb --- /dev/null +++ b/interim-server/templates/forms/form_index.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block title %}{{ portal.name }}{% endblock %} + +{% block head_extras %} + +{% endblock %} + +{% block main %} +
+
+
+ {% for field in fields %} +
+ {% 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 %} + + {% when TextInputMode::MultiLine %} + + {% else %} + {% endmatch %} + {% else %} + {% endmatch %} + {% endif %} +
+ {% endfor %} +
+
+ +
+
+
+{% endblock %} diff --git a/interim-server/templates/relations_single/form_index.html b/interim-server/templates/relations_single/form_index.html index 379085a..3c2a591 100644 --- a/interim-server/templates/relations_single/form_index.html +++ b/interim-server/templates/relations_single/form_index.html @@ -3,6 +3,11 @@ {% block main %}
diff --git a/sass/form.scss b/sass/form.scss new file mode 100644 index 0000000..c28ef94 --- /dev/null +++ b/sass/form.scss @@ -0,0 +1,10 @@ +@use 'globals'; + +.phono-form{ + &__container { + margin: 0 auto; + max-width: 48rem; + padding: 2rem; + position: relative; + } +} diff --git a/sass/main.scss b/sass/main.scss index 4a343ed..48f82a9 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -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;