commit 510472b1160ab67782127177c6e131bd66dbb070 Author: Paul Walko Date: Fri Jul 16 08:09:27 2021 -0400 init commit to reduce size diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..962504e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +cavepedia-data +cavepedia.bleve +cavepedia.env +**/cavepedia_* +cavepedia-*.zip diff --git a/README.md b/README.md new file mode 100644 index 0000000..9051ad2 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# cavepedia + +## Requirements + +- go 1.16+ + + +## Add more documents + +1. Download documents to `/bigdata/archive/cavepedia/cavepedia-data/00_files` +1. Process documents to generate json files +1. Create a new release + + +## Run + +1. Download latest release +1. Run it + + +## Run in docker + +1. Run `./docker.sh up` + + +## Release + +On a reasonably powerful PC that can access `/bigdata`: + +1. Remove `cavepedia.bleve` if it exists +1. Run `./launch.sh build release` to build linux and windows binaries +1. Run `./launch.sh run` to index documents +1. Run `./launch.sh release` to bundle the data, binaries, docker.sh script, and index data into a zip +1. Copy the zip to `/bigdata/archive/cavepedia/release/cavepedia-X.Y.zip` + + +## TODO + +- highlight fuzzy matching +- speed up pdf loading +- Remove cavepedia-data repo eventually diff --git a/docker.sh b/docker.sh new file mode 100755 index 0000000..41d79e2 --- /dev/null +++ b/docker.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +set -e + +RELEASE=2.2 + +build () { + echo "Building cavepedia/cavepedia:$RELEASE" + docker build -t cavepedia/cavepedia:$RELEASE ./src +} + +import_data () { + echo "Importing cavepedia.bleve version $RELEASE" + if [ ! -f ./cavepedia-$RELEASE.zip ]; then + cp -r /bigdata/archive/cavepedia/release/cavepedia-$RELEASE.zip . + unzip cavepedia-$RELEASE.zip + fi +} + +up () { + build + import_data + + docker network create pew-net || true + + # Exposed on port 3000 on pew-net + docker run \ + --detach \ + --name cavepedia \ + --restart unless-stopped \ + --env PROXY=1 \ + --env-file cavepedia.env \ + --volume $PWD/cavepedia-$RELEASE/cavepedia-data:/go/src/app/cavepedia-data:ro \ + --volume $PWD/cavepedia-$RELEASE/cavepedia.bleve:/go/src/app/cavepedia.bleve:rw \ + --network pew-net \ + cavepedia/cavepedia:$RELEASE +} + +down () { + docker stop cavepedia || true + docker rm cavepedia || true +} + +logs () { + docker logs --follow cavepedia +} + + +$@ diff --git a/launch.sh b/launch.sh new file mode 100755 index 0000000..a3be588 --- /dev/null +++ b/launch.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +set -e + +RELEASE=2.2 +ln -sn /bigdata/archive/cavepedia/cavepedia-data ./cavepedia-data || true + +build_docker () { + docker rm cavepedia-build || true + docker build -t cavepedia/cavepedia-build:latest ./src + docker create --name cavepedia-build cavepedia/cavepedia-build:latest + docker cp cavepedia-build:/go/src/app/cavepedia_linux_amd64 ./src/cavepedia_linux_amd64 + docker cp cavepedia-build:/go/src/app/cavepedia_windows_amd64.exe ./src/cavepedia_windows_amd64.exe +} + +build () { + pushd ./src &>/dev/null + + go get honnef.co/go/tools/cmd/staticcheck + go get github.com/dgrijalva/jwt-go + go get github.com/zserge/lorca + + gofmt -w . + staticcheck . + CGO_ENABLED=1 GOARCH=amd64 go build -o ./cavepedia_linux_amd64 -v . + + if [[ "$1" == "release" ]]; then + CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc go build -ldflags -H=windowsgui -o ./cavepedia_windows_amd64.exe -v . + fi + + popd &>/dev/null +} + +# Default: bypass password and open in webview +run () { + ./src/cavepedia_linux_amd64 +} + +# Disable webview, enable password +proxy () { + PROXY=1 PASSWORD=test ./src/cavepedia_linux_amd64 +} + +# Disable webview only +proxy_test () { + PROXY=1 ./src/cavepedia_linux_amd64 +} + +release () { + mkdir cavepedia-$RELEASE + mv src/{cavepedia_linux_amd64,cavepedia_windows_amd64.exe} ./cavepedia-$RELEASE/ + mv cavepedia.bleve ./cavepedia-$RELEASE + mkdir cavepedia-$RELEASE/cavepedia-data/ + cp -r cavepedia-data/{00_files,02_json} ./cavepedia-$RELEASE/cavepedia-data/ + + zip -r cavepedia-$RELEASE.zip ./cavepedia-$RELEASE + sudo cp cavepedia-$RELEASE.zip /bigdata/archive/cavepedia/release/ + rm -rf cavepedia-$RELEASE +} + + +$@ diff --git a/src/Dockerfile b/src/Dockerfile new file mode 100644 index 0000000..5a506c3 --- /dev/null +++ b/src/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.16-buster + +LABEL org.opencontainers.image.source https://git.seaturtle.pw/pew/cavepedia +LABEL maintainer="Paul Walko " + +WORKDIR /go/src/app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=1 GOARCH=amd64 go build -o ./cavepedia_linux_amd64 -v . +RUN CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc go build -ldflags -H=windowsgui -o ./cavepedia_windows_amd64.exe -v . + +EXPOSE 3000 +CMD ["./cavepedia_linux_amd64"] diff --git a/src/common.go b/src/common.go new file mode 100644 index 0000000..4bc10ca --- /dev/null +++ b/src/common.go @@ -0,0 +1,31 @@ +package main + +import ( + "log" + "net/http" +) + +func checkError(err error) bool { + if err != nil { + log.Print("ERROR! " + err.Error()) + return false + } + return true +} + +/* HTTP */ +func checkWebError(w http.ResponseWriter, err error) bool { + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return false + } + return true +} + +func getURLParam(r *http.Request, param string) string { + paramList, exists := r.URL.Query()[param] + if !exists { + return "" + } + return paramList[0] +} diff --git a/src/cookies.go b/src/cookies.go new file mode 100644 index 0000000..7fc13a9 --- /dev/null +++ b/src/cookies.go @@ -0,0 +1,90 @@ +package main + +import ( + "fmt" + "net/http" + + "git.seaturtle.pw/pew/cavepedia/utils" + "github.com/dgrijalva/jwt-go" +) + +// Key for signing JWTs +var key []byte + +func deleteCookie(w http.ResponseWriter, name string, domain string) { + cookie := http.Cookie{ + Domain: domain, + MaxAge: -1, + Name: name, + Path: "/", + Value: "", + } + http.SetCookie(w, &cookie) +} + +func setCookie(w http.ResponseWriter, name string, domain string, value string) { + cookie := http.Cookie{ + Domain: domain, + Name: name, + Path: "/", + Value: value, + } + http.SetCookie(w, &cookie) +} + +// 1st -> valid auth?, 2nd bool -> any errors? +func getJWT(w http.ResponseWriter, r *http.Request) (bool, bool) { + tokenCookie, err := r.Cookie("CAVEPEDIA_SESSION") + if err != nil { + // Cookie does not exist + return false, true + } + + // Parse Cookie + tokenStr := tokenCookie.Value + token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { + // Verify signing method + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return key, nil + }) + + // Corrupt JWT + if err != nil { + deleteCookie(w, "CAVEPEDIA_SESSION", utils.GetConfig().CookieDomain) + return false, true + } + + claims, ok := token.Claims.(jwt.MapClaims) + // Issue looking up claims or invalid signature + if !ok || !token.Valid { + deleteCookie(w, "CAVEPEDIA_SESSION", utils.GetConfig().CookieDomain) + return false, true + } + + // Expired + err = claims.Valid() + if err != nil { + if setJWT(w) { + return true, true + } else { + return false, false + } + } + return true, true +} + +func setJWT(w http.ResponseWriter) bool { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ + Subject: "CAVEPEDIA", + }) + + tokenStr, err := token.SignedString(key) + if !checkWebError(w, err) { + return false + } + + setCookie(w, "CAVEPEDIA_SESSION", utils.GetConfig().CookieDomain, tokenStr) + return true +} diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..5d88bf3 --- /dev/null +++ b/src/go.mod @@ -0,0 +1,11 @@ +module git.seaturtle.pw/pew/cavepedia + +go 1.16 + +require ( + github.com/blevesearch/bleve/v2 v2.0.2 + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/kisielk/gotool v1.0.0 // indirect + github.com/zserge/lorca v0.1.10 + honnef.co/go/tools v0.2.0 // indirect +) diff --git a/src/go.sum b/src/go.sum new file mode 100644 index 0000000..08ca27b --- /dev/null +++ b/src/go.sum @@ -0,0 +1,142 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo= +github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/blevesearch/bleve/v2 v2.0.2 h1:D93VfhOiR6wALovgjsK5XNPeDRrZQlUEIq4YWFeaiTw= +github.com/blevesearch/bleve/v2 v2.0.2/go.mod h1:ip+4iafiEq2gCY5rJXe87bT6LkF/OJMCjQEYIfTBfW8= +github.com/blevesearch/bleve_index_api v1.0.0 h1:Ds3XeuTxjXCkG6pgIwWDRyooJKNIuOKemnN0N0IkhTU= +github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= +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/mmap-go v1.0.2 h1:JtMHb+FgQCTTYIhtMvimw15dJwu1Y5lrZDMOFXVWPk0= +github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA= +github.com/blevesearch/scorch_segment_api/v2 v2.0.1 h1:fd+hPtZ8GsbqPK1HslGp7Vhoik4arZteA/IsCEgOisw= +github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU= +github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac= +github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= +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.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU= +github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q= +github.com/blevesearch/vellum v1.0.3 h1:U86G41A7CtXNzzpIJHM8lSTUqz1Mp8U870TkcdCzZc8= +github.com/blevesearch/vellum v1.0.3/go.mod h1:2u5ax02KeDuNWu4/C+hVQMD6uLN4txH1JbtpaDNLJRo= +github.com/blevesearch/zapx/v11 v11.2.0 h1:GBkCJYsyj3eIU4+aiLPxoMz1PYvDbQZl/oXHIBZIP60= +github.com/blevesearch/zapx/v11 v11.2.0/go.mod h1:gN/a0alGw1FZt/YGTo1G6Z6XpDkeOfujX5exY9sCQQM= +github.com/blevesearch/zapx/v12 v12.2.0 h1:dyRcSoZVO1jktL4UpGkCEF1AYa3xhKPirh4/N+Va+Ww= +github.com/blevesearch/zapx/v12 v12.2.0/go.mod h1:fdjwvCwWWwJW/EYTYGtAp3gBA0geCYGLcVTtJEZnY6A= +github.com/blevesearch/zapx/v13 v13.2.0 h1:mUqbaqQABp8nBE4t4q2qMyHCCq4sykoV8r7aJk4ih3s= +github.com/blevesearch/zapx/v13 v13.2.0/go.mod h1:o5rAy/lRS5JpAbITdrOHBS/TugWYbkcYZTz6VfEinAQ= +github.com/blevesearch/zapx/v14 v14.2.0 h1:UsfRqvM9RJxKNKrkR1U7aYc1cv9MWx719fsAjbF6joI= +github.com/blevesearch/zapx/v14 v14.2.0/go.mod h1:GNgZusc1p4ot040cBQMRGEZobvwjCquiEKYh1xLFK9g= +github.com/blevesearch/zapx/v15 v15.2.0 h1:ZpibwcrrOaeslkOw3sJ7npP7KDgRHI/DkACjKTqFwyM= +github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= +github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM= +github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= +github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= +github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zserge/lorca v0.1.10 h1:f/xBJ3D3ipcVRCcvN8XqZnpoKcOXV8I4vwqlFyw7ruc= +github.com/zserge/lorca v0.1.10/go.mod h1:bVmnIbIRlOcoV285KIRSe4bUABKi7R7384Ycuum6e4A= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= +honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +honnef.co/go/tools v0.2.0 h1:ws8AfbgTX3oIczLPNPCu5166oBg9ST2vNs0rcht+mDE= +honnef.co/go/tools v0.2.0/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..423f1db --- /dev/null +++ b/src/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "crypto/rand" + "embed" + "log" + "net/http" + "runtime" + + "git.seaturtle.pw/pew/cavepedia/utils" + "github.com/blevesearch/bleve/v2" + "github.com/zserge/lorca" +) + +var JSON string +var index bleve.Index + +//go:embed templates +var templatesHTML embed.FS + +//go:embed static +var staticFiles embed.FS + +func main() { + log.SetFlags(log.Lshortfile) + FILES := "./cavepedia-data/00_files/" + JSON = "./cavepedia-data/02_json" + INDEX := "cavepedia.bleve" + + /* Setup Search */ + exists, ok := openIndex(INDEX) + if !ok { + return + } + + /* Web Server */ + key = make([]byte, 512) + _, err := rand.Read(key) + if err != nil { + log.Fatal(err) + } + http.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir(FILES)))) + http.Handle("/static/", http.FileServer(http.FS(staticFiles))) + http.Handle("/favicon.ico", http.RedirectHandler("/static/favicon.ico", http.StatusMovedPermanently)) + http.HandleFunc("/", GetHome) // Redirects to /search if auth'd + http.HandleFunc("/search", GetSearch) // Redirects to / if not auth'd + http.HandleFunc("/quiz", PostQuiz) + http.HandleFunc("/pdf", GetPDF) + + /* Index data if needed */ + if !exists && !indexDocuments() { + return + } + + if utils.GetConfig().Proxy { + log.Println("Server is running!") + log.Fatal(http.ListenAndServe(":3000", nil)) + } else { + // Start in separate goroutine + go func() { + log.Println("Server is running!") + log.Fatal(http.ListenAndServe(":3000", nil)) + }() + + /* GUI */ + args := []string{} + if runtime.GOOS == "linux" { + args = append(args, "--class=Lorca") + } + ui, err := lorca.New("http://localhost:3000", "", 1000, 800, args...) + if err != nil { + log.Fatal(err) + } + defer ui.Close() + <-ui.Done() + } +} diff --git a/src/search.go b/src/search.go new file mode 100644 index 0000000..36ae249 --- /dev/null +++ b/src/search.go @@ -0,0 +1,128 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + // "strings" + + "github.com/blevesearch/bleve/v2" + "github.com/blevesearch/bleve/v2/search" +) + +type pageStruct struct { + Timestamp string `json:"timestamp"` + Group string `json:"group"` + Category string `json:"category"` + Season string `json:"season"` + File string `json:"file"` + PageNum int `json:"pageNum"` + Readability float32 `json:"readability"` + Text string `json:"text"` +} + +// Do search and return results +// Query reference: https://pkg.go.dev/github.com/blevesearch/bleve@v1.0.14/search/query#MatchQuery +func doSearch(term string, fuzziness int) (search.DocumentMatchCollection, error) { + // Single word + var search *bleve.SearchRequest + query := bleve.NewQueryStringQuery(term) + search = bleve.NewSearchRequestOptions(query, 100, 0, false) + + search.Fields = []string{"*"} + results, err := index.Search(search) + if err != nil { + return nil, err + } + return results.Hits, nil +} + +// Create index if it doesn't already exist and return it +func openIndex(path string) (bool, bool) { + if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { + // Index does not exist + var err error + index, err = bleve.New(path, bleve.NewIndexMapping()) + if !checkError(err) { + return false, false + } + return false, true + } + + // Index already exists + var err error + index, err = bleve.Open(path) + if !checkError(err) { + return true, false + } + return true, true +} + +// Index all documents +func indexDocuments() bool { + log.Println("Indexing documents...") + + GRPS, err := ioutil.ReadDir(JSON) + if !checkError(err) { + return false + } + + for _, GRP := range GRPS { + // Iterate through groups + GRP_PATH := JSON + "/" + GRP.Name() + CATS, _ := ioutil.ReadDir(GRP_PATH) + if !checkError(err) { + return false + } + + for _, CAT := range CATS { + // Iterate through categories + CAT_PATH := GRP_PATH + "/" + CAT.Name() + FILES, _ := ioutil.ReadDir(CAT_PATH) + if !checkError(err) { + return false + } + + for _, FILE := range FILES { + // Iterate through files + FILE_PATH := CAT_PATH + "/" + FILE.Name() + PAGES, _ := ioutil.ReadDir(FILE_PATH) + if !checkError(err) { + return false + } + + for _, PAGE := range PAGES { + // Iterate through pages + PAGE_PATH := FILE_PATH + "/" + PAGE.Name() + + // Read file + f, _ := os.Open(PAGE_PATH) + if !checkError(err) { + return false + } + b, _ := ioutil.ReadAll(f) + if !checkError(err) { + return false + } + err = f.Close() + if !checkError(err) { + return false + } + + // Index file + var page pageStruct + json.Unmarshal(b, &page) + + ID := GRP.Name() + "-" + CAT.Name() + "-" + FILE.Name() + "-" + PAGE.Name() + err = index.Index(ID, page) + if !checkError(err) { + return false + } + } + } + } + } + + return true +} diff --git a/src/static/cavepedia-pdf.css b/src/static/cavepedia-pdf.css new file mode 100644 index 0000000..e7d23d5 --- /dev/null +++ b/src/static/cavepedia-pdf.css @@ -0,0 +1,6 @@ +body { + background-color: darkgrey; + font-size: 18px; + line-height: 1.6; + text-align: center; +} diff --git a/src/static/cavepedia.css b/src/static/cavepedia.css new file mode 100644 index 0000000..c44279b --- /dev/null +++ b/src/static/cavepedia.css @@ -0,0 +1,178 @@ +body { + background-color: darkgray; + font-size: 18px; + line-height: 1.6; + margin: 40px auto; + max-width: 700px; + text-align: center; +} + +body p, #howto { + text-align: left; +} + +#search { + height: 25px; + width: 300px; +} + +/* bat */ +.bat-container { + width: 125px; + height: 170px; + margin: auto; + bottom: 80px; +} + +.pin-bottom { + right: 0; + bottom: 0; + left: 0; +} + +.bat-wrapper { + position: absolute; + height: 170px; + width: 125px; + animation: ease-in-out wiggle 7s 0s infinite alternate both; +} + +.bat { + background-size: 2250px 170px; + background-image: url('https://www.mapbox.com/bites/00279/bat.svg'); + height: 170px; + width: 125px; + animation: flapping .4s steps(18) infinite alternate; +} + +.bat-leg-1 { + bottom: 54px; + left: 55px; + display: block; + width: 4px; + height: 15px; + position: absolute; + border-radius: 12px; + background: #008dba; + animation: ease-in-out leg 2s 0s infinite alternate both; +} + +.bat-leg-2 { + bottom: 54px; + left: 65px; + display: block; + width: 4px; + height: 15px; + position: absolute; + border-radius: 12px; + background: #008dba; + animation: ease-in-out leg2 2s 0s infinite alternate both; +} + +.bat-shadow { + position: relative; + width: 20px; + height: 10px; + border-radius: 50%; + background-color: #005773; + opacity: .2; + top: 180px; + left: 60px; + animation: ease-in-out bat-shadow 7s 0s infinite alternate both; +} + +@keyframes wiggle { + 0% { + transform: rotate( 0deg) translateY(-22px) scale(1.0); + } + 33% { + transform: rotate( -10deg) translateY(0px) scale(1.05) translateX(-15px); + } + 50% { + transform: rotate( 0deg) translateY(7px) scale(1.0) translateX(5px); + } + 66% { + transform: rotate( 5deg) translateY(15px); + } + 75% { + transform: rotate( 0deg) translateY(8px); + } + 100% { + transform: rotate( 10deg) translateY(0px) translateX(-15px); + } +} + +@-webkit-keyframes wiggle { + 0% { + transform: rotate( 0deg) translateY(-22px) scale(1.0); + } + 33% { + transform: rotate( -10deg) translateY(0px) scale(1.05) translateX(-15px); + } + 50% { + transform: rotate( 0deg) translateY(7px) scale(1.0) translateX(5px); + } + 66% { + transform: rotate( 5deg) translateY(15px); + } + 75% { + transform: rotate( 0deg) translateY(8px); + } + 100% { + transform: rotate( 10deg) translateY(0px) translateX(-15px); + } +} + +@keyframes flapping { + 100% { + background-position: -2250px 0px; + } +} + +@-webkit-keyframes flapping { + 100% { + background-position: -2250px 0px; + } +} + +@keyframes bat-shadow { + 0% { + transform: scale(.7) translateX(0px); + } + 33% { + transform: scale(1) translateX(-5px); + } + 50% { + transform: scale(1.1) translateX(4px); + } + 66% { + transform: scale(1.3); + } + 75% { + transform: scale(1.2); + } + 100% { + transform: scale(1.0) translateX(-5px); + } +} + +@-webkit-keyframes bat-shadow { + 0% { + transform: scale(.7) translateX(0px); + } + 33% { + transform: scale(1) translateX(-5px); + } + 50% { + transform: scale(1.1) translateX(4px); + } + 66% { + transform: scale(1.3); + } + 75% { + transform: scale(1.2); + } + 100% { + transform: scale(1.0) translateX(-5px); + } +} diff --git a/src/static/favicon.ico b/src/static/favicon.ico new file mode 100644 index 0000000..2c35459 Binary files /dev/null and b/src/static/favicon.ico differ diff --git a/src/templates/index.html.tmpl b/src/templates/index.html.tmpl new file mode 100644 index 0000000..5ae1b24 --- /dev/null +++ b/src/templates/index.html.tmpl @@ -0,0 +1,35 @@ + + + + Cavepedia + + + +

Welcome to Cavepedia

+ + {{if .TryAgain}} +

Incorrect! Please try again!

+ {{else}} +

Please complete this quiz to continue

+ {{end}} +
+ A.I. + +
+ + +
+
+
+
+
+
+
+
+
+ + + + diff --git a/src/templates/pdf.html.tmpl b/src/templates/pdf.html.tmpl new file mode 100644 index 0000000..119a2b1 --- /dev/null +++ b/src/templates/pdf.html.tmpl @@ -0,0 +1,19 @@ + + + + {{.Filename}} + + + + <-- Back to Results +
+ Term: {{.Term}} | + Category: {{.Category}} | + Filename: {{.Filename}} | + Season: {{.Season}} | + Page Number: {{.PageNum}} +
+ + + + diff --git a/src/templates/results.html.tmpl b/src/templates/results.html.tmpl new file mode 100644 index 0000000..9467981 --- /dev/null +++ b/src/templates/results.html.tmpl @@ -0,0 +1,67 @@ + + + + Cavepedia + + + + <-- Back to Search +
+ + {{$numResults := .Results.Len}} + {{$term := .Term}} + +

{{$numResults}} results for {{$term}}

+

(Only exact matches are highlighted)

+ + {{if not .Results}} +

No Results :(

+ {{end}} + + + {{range $i, $r := .Results}} + {{$group := index .Fields "group"}} + {{$category := index .Fields "category"}} + {{$filename := index .Fields "file"}} + {{$pageNum := index .Fields "pageNum"}} + {{$season := index .Fields "season"}} + +
+

+ {{$filename}} +

+ Result {{$i}} of {{$numResults}} | + Category: {{index .Fields "category"}} | + Season: {{index .Fields "season"}} | + Page Number: {{index .Fields "pageNum"}} +

{{index .Fields "text"}}

+

+ {{end}} + + + + + diff --git a/src/templates/search.html.tmpl b/src/templates/search.html.tmpl new file mode 100644 index 0000000..0e262df --- /dev/null +++ b/src/templates/search.html.tmpl @@ -0,0 +1,117 @@ + + + + Cavepedia + + + +

Welcome to Cavepedia

+
+ +
+ +
+ + {{if .ErrorMsg}} +

{{.ErrorMsg}}

+ {{end}} + +
+

How to Search

+

Data is organized by group, then by category:

+
    +
  • nss
  • +
      +
    • compasstape
    • +
    • nylonhighway
    • +
    • speleonics
    • +
    +
  • var
  • +
      +
    • fyi
    • +
    • regionrecord
    • +
    +
  • vpi
  • +
      +
    • grapevine
    • +
    • signout
    • +
    • trog
    • +
    +
+ +

Examples

+
    +
  • Tip: Multiple different searches with simple queries work better than a single complicated query
  • +
  • Bolt hanger stats
  • +
      +
    • +category:nylonhighway +bolt +hanger +stats (This actually returns no results, but is a good example of being too strict).
    • +
    • +category:nylonhighway +hanger +strength +test
    • +
    +
  • Suunto Repair
  • +
      +
    • +category:compasstape +suunto +repair
    • +
    +
  • How to use a QAS
  • +
      +
    • +category:nylonhighway +QAS
    • +
    +
  • Newberry trips Steve Wells went on
  • +
      +
    • +category:signout +wells +newberries
    • +
    • +category:signout +wells +newberry's
    • +
    • Before 2000: +category:signout timestamp:<"2000-01-01" +wells /(newberries|newberry's)/
    • +
    +
  • Weird spellings of things
  • +
      +
    • /Eric (landgraf|landgraff)/
    • +
    +
  • Legible Trog articles
  • +
      +
    • +category:trog +readability:>9
    • +
    +
+ +

Search Operators

+
    +
  • + -- must include
  • +
  • - -- must not include
  • +
  • <, >, <=, >= -- must be greater than or less than. Only works with numeric fields. Cannot use both at the same time!
  • +
  • Regular Expressions are supported
  • +
  • No operator: should include
  • +
+ +

Available Fields

+
    +
  • Very useful fields
  • +
      +
    • group, string -- See above (nss, var, vpi, etc).
    • +
    • category, string -- See above
    • +
    • timestamp, number -- Approximate date of article. Based completely on dates in filenames (not very accurate).
    • +
    +
  • Not quite as useful fields
  • +
      +
    • season, string -- Approximate date of article. Based completely on season or date in filename (not very accurate).
    • +
    • file, string -- PDF filename
    • +
    • pageNum, number -- Article page number in the PDF.
    • +
    • readability -- Uses the Dale-Chall readability formula
    • +
    • text, string -- Actual article text
    • +
    +
+
+ + +
+
+
+
+
+
+
+
+
+ + + + diff --git a/src/utils/config.go b/src/utils/config.go new file mode 100644 index 0000000..c30a62a --- /dev/null +++ b/src/utils/config.go @@ -0,0 +1,19 @@ +package utils + +import ( + "os" +) + +type Config struct { + CookieDomain string + Password string + Proxy bool +} + +func GetConfig() *Config { + return &Config{ + CookieDomain: os.Getenv("COOKIE_DOMAIN"), + Password: os.Getenv("PASSWORD"), + Proxy: os.Getenv("PROXY") == "1", + } +} diff --git a/src/web.go b/src/web.go new file mode 100644 index 0000000..5c38d84 --- /dev/null +++ b/src/web.go @@ -0,0 +1,171 @@ +package main + +import ( + "html/template" + "net/http" + "strings" + + "git.seaturtle.pw/pew/cavepedia/utils" + "github.com/blevesearch/bleve/v2/search" +) + +func GetHome(w http.ResponseWriter, r *http.Request) { + // Bypass auth + if utils.GetConfig().Password == "" { + http.Redirect(w, r, "/search", http.StatusTemporaryRedirect) + return + } + + // if auth'd --> redirect to / + auth, ok := getJWT(w, r) + if !ok { + return + } + if auth { + http.Redirect(w, r, "/search", http.StatusTemporaryRedirect) + return + } + + // return index.html + t, err := template.ParseFS(templatesHTML, "templates/index.html.tmpl") + if !checkWebError(w, err) { + return + } + + err = t.Execute(w, struct { + TryAgain bool + }{ + TryAgain: false, + }) + checkError(err) +} + +func GetSearch(w http.ResponseWriter, r *http.Request) { + // Check auth + if utils.GetConfig().Password != "" { + // if not auth'd --> redirect to / + auth, ok := getJWT(w, r) + if !ok { + return + } + if !auth { + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + return + } + } + + // Lookup params + _, exists := r.URL.Query()["exact"] + fuzziness := 2 + if exists { + fuzziness = 0 + } + + errorMsg := getURLParam(r, "error") + placeholder := getURLParam(r, "placeholder") + term := getURLParam(r, "term") + + // We don't actually pass the message in, just true if there's an error + if errorMsg != "" { + errorMsg = "Invalid query syntax! Please try again." + } + + // Invalid query and/or show placeholder + if errorMsg != "" || term == "" { + t, err := template.ParseFS(templatesHTML, "templates/search.html.tmpl") + if !checkWebError(w, err) { + return + } + + err = t.Execute(w, struct { + ErrorMsg string + Placeholder string + }{ + ErrorMsg: errorMsg, + Placeholder: placeholder, + }) + checkError(err) + return + } + + // Do search + results, err := doSearch(term, fuzziness) + if err != nil { + http.Redirect(w, r, "/search?error=true&placeholder="+term, http.StatusTemporaryRedirect) + return + } + + t, err := template.ParseFS(templatesHTML, "templates/results.html.tmpl") + if !checkWebError(w, err) { + return + } + err = t.Execute(w, struct { + Results search.DocumentMatchCollection + Term string + }{ + Results: results, + Term: term, + }) + checkError(err) +} + +func PostQuiz(w http.ResponseWriter, r *http.Request) { + if !checkWebError(w, r.ParseForm()) { + return + } + + // valid quiz + if strings.EqualFold(r.Form.Get("answer"), utils.GetConfig().Password) { + // create JWT + if !setJWT(w) { + return + } + http.Redirect(w, r, "/search", http.StatusTemporaryRedirect) + return + } + + // invalid quiz + t, err := template.ParseFS(templatesHTML, "templates/index.html.tmpl") + if !checkWebError(w, err) { + return + } + err = t.Execute(w, struct { + TryAgain bool + }{ + TryAgain: true, + }) + checkError(err) +} + +func GetPDF(w http.ResponseWriter, r *http.Request) { + category := getURLParam(r, "category") + file := getURLParam(r, "file") + filename := getURLParam(r, "filename") + pageNum := getURLParam(r, "pageNum") + season := getURLParam(r, "season") + term := getURLParam(r, "term") + + t, err := template.ParseFS(templatesHTML, "templates/pdf.html.tmpl") + if !checkWebError(w, err) { + return + } + + err = t.Execute(w, struct { + Category string + File string + Filename string + PageNum string + Season string + Term string + }{ + Category: category, + File: file, + Filename: filename, + PageNum: pageNum, + Season: season, + Term: term, + }) + if !checkError(err) { + return + } +}