diff --git a/go.mod b/go.mod index 7726d24..a0ee61a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.3 require ( github.com/a-h/templ v0.2.793 github.com/andybalholm/brotli v1.1.1 + github.com/blevesearch/bleve/v2 v2.4.3 github.com/brianvoe/gofakeit/v7 v7.1.2 github.com/romshark/templier v0.8.0 github.com/stretchr/testify v1.9.0 @@ -12,9 +13,28 @@ require ( require ( github.com/PuerkitoBio/goquery v1.10.0 // indirect + github.com/RoaringBitmap/roaring v1.9.3 // indirect github.com/a-h/parse v0.0.0-20240121214402-3caf7543159a // indirect github.com/a-h/protocol v0.0.0-20240821172110-e94e5c43897f // indirect github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/bits-and-blooms/bitset v1.12.0 // indirect + github.com/blevesearch/bleve_index_api v1.1.12 // indirect + github.com/blevesearch/geo v0.1.20 // indirect + github.com/blevesearch/go-faiss v1.0.23 // indirect + github.com/blevesearch/go-porterstemmer v1.0.3 // indirect + github.com/blevesearch/gtreap v0.1.1 // indirect + github.com/blevesearch/mmap-go v1.0.4 // indirect + github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect + github.com/blevesearch/segment v0.9.1 // indirect + github.com/blevesearch/snowballstem v0.9.0 // indirect + github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect + github.com/blevesearch/vellum v1.0.10 // indirect + github.com/blevesearch/zapx/v11 v11.3.10 // indirect + github.com/blevesearch/zapx/v12 v12.3.10 // indirect + github.com/blevesearch/zapx/v13 v13.3.10 // indirect + github.com/blevesearch/zapx/v14 v14.3.10 // indirect + github.com/blevesearch/zapx/v15 v15.3.16 // indirect + github.com/blevesearch/zapx/v16 v16.1.8 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cli/browser v1.3.0 // indirect @@ -26,15 +46,21 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/gobwas/glob v0.2.3 // indirect + github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect + github.com/golang/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mschoch/smat v0.2.0 // indirect github.com/natefinch/atomic v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/romshark/yamagiconf v1.0.2 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/encoding v0.4.0 // indirect + go.etcd.io/bbolt v1.3.7 // indirect go.lsp.dev/jsonrpc2 v0.10.0 // indirect go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect go.lsp.dev/uri v0.3.0 // indirect diff --git a/go.sum b/go.sum index 574654c..29e0747 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= +github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= +github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= github.com/a-h/parse v0.0.0-20240121214402-3caf7543159a h1:vlmAfVwFK9sRpDlJyuHY8htP+KfGHB2VH02u0SoIufk= github.com/a-h/parse v0.0.0-20240121214402-3caf7543159a/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= github.com/a-h/protocol v0.0.0-20240821172110-e94e5c43897f h1:R2w1J4LdHLN8j0yzh+LPGDIeCz+INVu/ocO83Fqe3KM= @@ -10,6 +12,44 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZtmFVPHmA= +github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/blevesearch/bleve/v2 v2.4.3 h1:XDYj+1prgX84L2Cf+V3ojrOPqXxy0qxyd2uLMmeuD+4= +github.com/blevesearch/bleve/v2 v2.4.3/go.mod h1:hEPDPrbYw3vyrm5VOa36GyS4bHWuIf4Fflp7460QQXY= +github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY= +github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8= +github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM= +github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w= +github.com/blevesearch/go-faiss v1.0.23 h1:Wmc5AFwDLKGl2L6mjLX1Da3vCL0EKa2uHHSorcIS1Uc= +github.com/blevesearch/go-faiss v1.0.23/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= +github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= +github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= +github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= +github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= +github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= +github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= +github.com/blevesearch/scorch_segment_api/v2 v2.2.16 h1:uGvKVvG7zvSxCwcm4/ehBa9cCEuZVE+/zvrSl57QUVY= +github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0= +github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= +github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= +github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= +github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= +github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= +github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= +github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI= +github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= +github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk= +github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ= +github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s= +github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs= +github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8= +github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk= +github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU= +github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns= +github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH48sb+FmE= +github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= +github.com/blevesearch/zapx/v16 v16.1.8 h1:Bxzpw6YQpFs7UjoCV1+RvDw6fmAT2GZxldwX8b3wVBM= +github.com/blevesearch/zapx/v16 v16.1.8/go.mod h1:JqQlOqlRVaYDkpLIl3JnKql8u4zKTNlVEa3nLsi0Gn8= github.com/brianvoe/gofakeit/v7 v7.1.2 h1:vSKaVScNhWVpf1rlyEKSvO8zKZfuDtGqoIHT//iNNb8= github.com/brianvoe/gofakeit/v7 v7.1.2/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -18,6 +58,7 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -36,11 +77,21 @@ github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27 github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede h1:YrgBGwxMRK0Vq0WSCWFaZUnTsrA/PZE/xs1QZh+/edg= +github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -52,6 +103,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -66,11 +119,15 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOVmy8= github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE= @@ -136,5 +193,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server/server.go b/server/server.go index 999f679..71c9fc7 100644 --- a/server/server.go +++ b/server/server.go @@ -31,8 +31,7 @@ type Server struct { addressCountryOptions []template.NamedOption shippingCompanyOptions []template.NamedOption - // In-memory state of the world simulation. - orders []domain.ShippingDetails + store *Store } var _ http.Handler = new(Server) @@ -50,6 +49,8 @@ func New(logAccess, logError *slog.Logger, conf Config) *Server { addressCountryOptions: newAddressCountryOptions(), shippingCompanyOptions: newShippingCompanyOptions(), + + store: NewStore(), } var handlerPublicAssets http.Handler @@ -189,10 +190,33 @@ func (s *Server) handleAutocompleteCity(w http.ResponseWriter, r *http.Request) func (s *Server) handleGetIndex(w http.ResponseWriter, r *http.Request) { theme := middleware.GetCtxTheme(r.Context()) + searchQuery := r.FormValue("searchQuery") + + orders, err := s.fetchOrders(searchQuery) + if err != nil { + s.errInternal(w, err) + return + } + + if r.Header.Get("HX-Request") != "" { + var f template.Form + f.UnmarshalForm(r) + f.ResetErrorsForZero() + if err := template.RenderViewIndex( + r.Context(), w, f, searchQuery, + s.addressCountryOptions, s.shippingCompanyOptions, orders, + ); err != nil { + s.errInternal(w, err) + return + } + return + } + + // Blank page load. if err := template.RenderPageIndex( - r.Context(), w, - theme == middleware.ThemeDark, - template.Form{}, s.addressCountryOptions, s.shippingCompanyOptions, s.orders, + r.Context(), w, theme == middleware.ThemeDark, + template.Form{}, searchQuery, + s.addressCountryOptions, s.shippingCompanyOptions, orders, ); err != nil { s.errInternal(w, err) return @@ -257,7 +281,7 @@ func (s *Server) handlePostOrders(w http.ResponseWriter, r *http.Request) { f.UnmarshalForm(r) if f.IsValid() { // Add order and display empty form. - s.orders = append(s.orders, domain.ShippingDetails{ + newOrder := domain.ShippingDetails{ CompanyName: f.ParsedCompanyName, ContactFirstName: f.ParsedFirstName, ContactLastName: f.ParsedLastName, @@ -273,14 +297,30 @@ func (s *Server) handlePostOrders(w http.ResponseWriter, r *http.Request) { Due: f.ParsedDue, ShippingCompany: f.ParsedShippingCompany, SpecialNotes: f.ParsedSpecialNotes, - }) + } + _, err := s.store.NewOrder(newOrder) + if err != nil { + s.errInternal(w, err) + return + } f = template.Form{} } + + searchQuery := r.FormValue("searchQuery") + + orders, err := s.fetchOrders(searchQuery) + if err != nil { + s.errInternal(w, err) + return + } + if err := template.RenderViewIndex( r.Context(), w, - f, s.addressCountryOptions, s.shippingCompanyOptions, s.orders, + f, searchQuery, + s.addressCountryOptions, s.shippingCompanyOptions, orders, ); err != nil { s.errInternal(w, err) + return } } @@ -290,3 +330,12 @@ func (s *Server) errInternal(w http.ResponseWriter, err error) { http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } + +func (s *Server) fetchOrders(searchQuery string) ( + orders []domain.ShippingDetails, err error, +) { + if searchQuery != "" { + return s.store.OrderBySearchQuery(searchQuery, 10) + } + return s.store.GetAllOrders() +} diff --git a/server/store.go b/server/store.go new file mode 100644 index 0000000..fa28e5c --- /dev/null +++ b/server/store.go @@ -0,0 +1,133 @@ +package server + +import ( + "fmt" + "strconv" + "sync" + "time" + + bleve "github.com/blevesearch/bleve/v2" + "github.com/romshark/demo-islands/domain" +) + +// Store is an in-memory state of the world simulation. +type Store struct { + lock sync.Mutex + orderIDCounter uint64 + orders map[string]domain.ShippingDetails + searchIndex bleve.Index +} + +func NewStore() *Store { + // Define the field mapping + textFieldMapping := bleve.NewTextFieldMapping() + textFieldMapping.Analyzer = "standard" // Default analyzer + + // Define the document mapping + docMapping := bleve.NewDocumentMapping() + + // Add fields for fuzzy search + docMapping.AddFieldMappingsAt("CompanyName", textFieldMapping) + docMapping.AddFieldMappingsAt("FirstName", textFieldMapping) + docMapping.AddFieldMappingsAt("LastName", textFieldMapping) + docMapping.AddFieldMappingsAt("Email", textFieldMapping) + docMapping.AddFieldMappingsAt("DestinationAddress", textFieldMapping) + docMapping.AddFieldMappingsAt("Phone", textFieldMapping) + docMapping.AddFieldMappingsAt("SpecialNotes", textFieldMapping) + + // Create the index mapping + indexMapping := bleve.NewIndexMapping() + indexMapping.DefaultMapping = docMapping + + // Create an in-memory index + searchIndex, err := bleve.NewMemOnly(indexMapping) + if err != nil { + panic(fmt.Errorf("creating in-memory bleve index: %w", err)) + } + return &Store{ + searchIndex: searchIndex, + orders: make(map[string]domain.ShippingDetails), + } +} + +// GetAllOrders returns all currently stored orders. +func (s *Store) GetAllOrders() ([]domain.ShippingDetails, error) { + s.lock.Lock() + defer s.lock.Unlock() + + results := make([]domain.ShippingDetails, 0, len(s.orders)) + for _, o := range s.orders { + results = append(results, o) + } + return results, nil +} + +// OrderBySearchQuery finds orders by generic search query with fuzzy matching. +func (s *Store) OrderBySearchQuery( + query string, limit int, +) ([]domain.ShippingDetails, error) { + s.lock.Lock() + defer s.lock.Unlock() + + // Use a fuzzy query for approximate matches. + q := bleve.NewFuzzyQuery(query) + // Allow small typos or approximate matches. + q.SetFuzziness(2) + + searchRequest := bleve.NewSearchRequest(q) + searchRequest.Size = limit + r, err := s.searchIndex.Search(searchRequest) + if err != nil { + return nil, err + } + + results := make([]domain.ShippingDetails, len(r.Hits)) + for i, hit := range r.Hits { + if order, ok := s.orders[hit.ID]; ok { + results[i] = order + } + } + return results, nil +} + +// NewOrder adds a new order and indexes it for search. +func (s *Store) NewOrder(order domain.ShippingDetails) (id string, err error) { + s.lock.Lock() + defer s.lock.Unlock() + + // Generate a new unique ID for the order. + s.orderIDCounter++ + id = strconv.FormatUint(s.orderIDCounter, 36) + + orderIndexData := map[string]interface{}{ + "CompanyName": order.CompanyName.String(), + "FirstName": order.ContactFirstName.String(), + "LastName": order.ContactLastName.String(), + "Email": order.ContactEmail.String(), + "DestinationAddress": fmt.Sprintf( + "%s | %s | %s", + order.DestinationAddress.Country.String(), + order.DestinationAddress.City.String(), + order.DestinationAddress.PostalCode.String(), + ), + "Phone": order.ContactPhone.String(), + "SpecialNotes": order.SpecialNotes.String(), + "Express": order.Express, + "Due": order.Due.Format(time.RFC3339), + "ShippingCompany": order.ShippingCompany.String(), + } + if err := s.searchIndex.Index(id, orderIndexData); err != nil { + return "", err + } + + s.orders[id] = order + return id, nil +} + +func (s *Store) RemoveOrder(id string) error { + s.lock.Lock() + defer s.lock.Unlock() + + delete(s.orders, id) + return s.searchIndex.Delete(id) +} diff --git a/server/template/template.go b/server/template/template.go index 483c0a2..b536ece 100644 --- a/server/template/template.go +++ b/server/template/template.go @@ -12,24 +12,27 @@ func RenderPageIndex( ctx context.Context, w io.Writer, darkMode bool, form Form, + searchQuery string, addressCountryOptions []NamedOption, shippingCompanyOptions []NamedOption, orders []domain.ShippingDetails, ) error { return pageIndex( - form, darkMode, addressCountryOptions, shippingCompanyOptions, orders, + form, searchQuery, darkMode, + addressCountryOptions, shippingCompanyOptions, orders, ).Render(ctx, w) } func RenderViewIndex( ctx context.Context, w io.Writer, form Form, + searchQuery string, addressCountryOptions []NamedOption, shippingCompanyOptions []NamedOption, orders []domain.ShippingDetails, ) error { return viewIndex( - form, addressCountryOptions, shippingCompanyOptions, orders, + form, searchQuery, addressCountryOptions, shippingCompanyOptions, orders, ).Render(ctx, w) } diff --git a/server/template/template.templ b/server/template/template.templ index 8d66ff7..7279051 100644 --- a/server/template/template.templ +++ b/server/template/template.templ @@ -52,6 +52,7 @@ templ htmlMain(title string, darkMode bool) { templ viewIndex( form Form, + searchQuery string, addressCountryOptions []NamedOption, shippingCompanyOptions []NamedOption, shippingOrders []domain.ShippingDetails, @@ -97,6 +98,32 @@ templ viewIndex( } +