init commit to reduce size
commit
510472b116
|
@ -0,0 +1,5 @@
|
|||
cavepedia-data
|
||||
cavepedia.bleve
|
||||
cavepedia.env
|
||||
**/cavepedia_*
|
||||
cavepedia-*.zip
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
$@
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
$@
|
|
@ -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"]
|
|
@ -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]
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
body {
|
||||
background-color: darkgrey;
|
||||
font-size: 18px;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -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>
|
|
@ -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}}"><-- 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>
|
||||
|
|
@ -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}}"><-- 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>
|
||||
|
|
@ -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's</code></li>
|
||||
<li>Before 2000: <code>+category:signout timestamp:<"2000-01-01" +wells /(newberries|newberry's)/</code></li>
|
||||
</ul>
|
||||
<li>Weird spellings of things</li>
|
||||
<ul>
|
||||
<li><code>/Eric (landgraf|landgraff)/</code></li>
|
||||
</ul>
|
||||
<li>Legible Trog articles</li>
|
||||
<ul>
|
||||
<li><code>+category:trog +readability:>9</code></li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<h3>Search Operators</h3>
|
||||
<ul>
|
||||
<li>+ -- must include</li>
|
||||
<li>- -- must not include</li>
|
||||
<li><, >, <=, >= -- 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>
|
|
@ -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",
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue