init commit to reduce size

master
Paul Walko 2021-07-16 08:09:27 -04:00
commit 510472b116
20 changed files with 1264 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
cavepedia-data
cavepedia.bleve
cavepedia.env
**/cavepedia_*
cavepedia-*.zip

41
README.md Normal file
View File

@ -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

49
docker.sh Executable file
View File

@ -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
}
$@

62
launch.sh Executable file
View File

@ -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
}
$@

16
src/Dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM golang:1.16-buster
LABEL org.opencontainers.image.source https://git.seaturtle.pw/pew/cavepedia
LABEL maintainer="Paul Walko <paulsw.pw@gmail.com>"
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"]

31
src/common.go Normal file
View File

@ -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]
}

90
src/cookies.go Normal file
View File

@ -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
}

11
src/go.mod Normal file
View File

@ -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
)

142
src/go.sum Normal file
View File

@ -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=

77
src/main.go Normal file
View File

@ -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()
}
}

128
src/search.go Normal file
View File

@ -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
}

View File

@ -0,0 +1,6 @@
body {
background-color: darkgrey;
font-size: 18px;
line-height: 1.6;
text-align: center;
}

178
src/static/cavepedia.css Normal file
View File

@ -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);
}
}

BIN
src/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Cavepedia</title>
<link rel="stylesheet" href="/static/cavepedia.css">
</head>
<body>
<h1>Welcome to Cavepedia</h1>
{{if .TryAgain}}
<h3>Incorrect! Please try again!</h3>
{{else}}
<h3>Please complete this quiz to continue</h3>
{{end}}
<form action="/quiz" method="post">
A.I. <input type="text" id="answer" placeholder="Answer..." name="answer">
<button type="submit">Submit</button>
</form>
<!-- bat -->
<div class="bat-container pin-bottom">
<div class="bat-wrapper">
<div class="bat js-bat">
<div class="bat-leg-1"></div>
<div class="bat-leg-2"></div>
</div>
</div>
<div class="bat-shadow"></div>
</div>
<script>
document.getElementById('answer').focus();
</script>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>{{.Filename}}</title>
<link rel="stylesheet" href="/static/cavepedia-pdf.css">
</head>
<body>
<a href="/search?term={{.Term}}">&lt-- Back to Results</a>
<br>
<strong>Term:</strong> {{.Term}} |
<strong>Category:</strong> {{.Category}} |
<strong>Filename:</strong> {{.Filename}} |
<strong>Season:</strong> {{.Season}} |
<strong>Page Number:</strong> {{.PageNum}}
<br>
<iframe src="{{.File}}#page={{.PageNum}}" style="width:100%;height:calc(100vh - 85px);"></iframe>
</body>
</html>

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<title>Cavepedia</title>
<link rel="stylesheet" href="/static/cavepedia.css">
</head>
<body>
<a href="/search?placeholder={{.Term}}">&lt-- Back to Search</a>
<br>
{{$numResults := .Results.Len}}
{{$term := .Term}}
<h2 id="results">{{$numResults}} results for {{$term}}</h2>
<h4>(Only exact matches are highlighted)</h4>
{{if not .Results}}
<h3>No Results :(</h3>
{{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"}}
<div id="{{$i}}">
<h3><a id="pdf_{{$i}}" href="/pdf?file=/files/{{$group}}/{{$category}}/{{$filename}}&category={{$category}}&filename={{$filename}}&season={{$season}}&pageNum={{$pageNum}}&term={{$term}}">
{{$filename}}</a>
</h3>
<strong id="id_{{$i}}">Result {{$i}} of {{$numResults}}</strong> |
<strong>Category:</strong> {{index .Fields "category"}} |
<strong>Season:</strong> {{index .Fields "season"}} |
<strong>Page Number:</strong> {{index .Fields "pageNum"}}
<p id="text_{{$i}}">{{index .Fields "text"}}<p>
</div>
{{end}}
<script>
const numResults = parseInt({{$numResults}});
const term = {{$term}};
const lower = term.toLowerCase();
const upper = term.toUpperCase();
const capitalize = term.charAt(0).toUpperCase() + term.slice(1);
for (i = 0; i < numResults; i++) {
// Highlight text
let newText = document.getElementById(`text_${i}`).innerHTML;
newText = newText.replace(lower, `<mark>${lower}</mark>`)
newText = newText.replace(upper, `<mark>${upper}</mark>`)
newText = newText.replace(capitalize, `<mark>${capitalize}</mark>`)
document.getElementById(`text_${i}`).innerHTML = newText;
// Set result number
document.getElementById(`id_${i}`).innerHTML = `Result ${i+1} of {{$numResults}}`;
// Link directly to PDF if mobile (doesn't support PDF natively)
if (window.matchMedia("(any-pointer: coarse)").matches) {
document.getElementById(`pdf_${i}`).href = document.getElementById(`pdf_${i}`).href.split('?')[1].split('&')[0].split('=')[1];
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html>
<head>
<title>Cavepedia</title>
<link rel="stylesheet" href="/static/cavepedia.css">
</head>
<body>
<h1>Welcome to Cavepedia</h1>
<form action="/search" method="GET">
<input type="text" id="search" onfocus="this.setSelectionRange(1000,1001);" placeholder="Search..." name="term" value="{{.Placeholder}}">
<br>
<button type="submit">Submit</button>
</form>
{{if .ErrorMsg}}
<h3>{{.ErrorMsg}}</h3>
{{end}}
<div id="howto">
<h2>How to Search</h2>
<p>Data is organized by <strong>group</strong>, then by <strong>category</strong>:</p>
<ul>
<li>nss</li>
<ul>
<li>compasstape</li>
<li>nylonhighway</li>
<li>speleonics</li>
</ul>
<li>var</li>
<ul>
<li>fyi</li>
<li>regionrecord</li>
</ul>
<li>vpi</li>
<ul>
<li>grapevine</li>
<li>signout</li>
<li>trog</li>
</ul>
</ul>
<h3>Examples</h3>
<ul>
<li>Tip: Multiple different searches with simple queries work better than a single complicated query</li>
<li>Bolt hanger stats</li>
<ul>
<li><code>+category:nylonhighway +bolt +hanger +stats</code> (This actually returns no results, but is a good example of being too strict).</li>
<li><code>+category:nylonhighway +hanger +strength +test</code></li>
</ul>
<li>Suunto Repair</li>
<ul>
<li><code>+category:compasstape +suunto +repair</code></li>
</ul>
<li>How to use a QAS</li>
<ul>
<li><code>+category:nylonhighway +QAS</code></li>
</ul>
<li>Newberry trips Steve Wells went on</li>
<ul>
<li><code>+category:signout +wells +newberries</code></li>
<li><code>+category:signout +wells +newberry&apos;s</code></li>
<li>Before 2000: <code>+category:signout timestamp:&lt;"2000-01-01" +wells &sol;(newberries|newberry&apos;s)&sol;</code></li>
</ul>
<li>Weird spellings of things</li>
<ul>
<li><code>&sol;Eric (landgraf|landgraff)/</code></li>
</ul>
<li>Legible Trog articles</li>
<ul>
<li><code>+category:trog +readability:&gt;9</code></li>
</ul>
</ul>
<h3>Search Operators</h3>
<ul>
<li>+ -- must include</li>
<li>- -- must not include</li>
<li>&lt;, &gt;, &lt;=, &gt;= -- must be greater than or less than. Only works with numeric fields. Cannot use both at the same time!</li>
<li>Regular Expressions are supported</li>
<li>No operator: should include</li>
</ul>
<h3>Available Fields</h3>
<ul>
<li>Very useful fields</li>
<ul>
<li>group, string -- See above (nss, var, vpi, etc).</li>
<li>category, string -- See above</li>
<li>timestamp, number -- Approximate date of article. Based completely on dates in filenames (not very accurate).</li>
</ul>
<li>Not quite as useful fields</li>
<ul>
<li>season, string -- Approximate date of article. Based completely on season or date in filename (not very accurate).</li>
<li>file, string -- PDF filename</li>
<li>pageNum, number -- Article page number in the PDF.</li>
<li>readability -- Uses the Dale-Chall readability formula</li>
<li>text, string -- Actual article text</li>
</ul>
</ul>
</div>
<!-- bat -->
<div class="bat-container pin-bottom">
<div class="bat-wrapper">
<div class="bat js-bat">
<div class="bat-leg-1"></div>
<div class="bat-leg-2"></div>
</div>
</div>
<div class="bat-shadow"></div>
</div>
<script>
document.getElementById('search').focus();
</script>
</body>
</html>

19
src/utils/config.go Normal file
View File

@ -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",
}
}

171
src/web.go Normal file
View File

@ -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
}
}