From c26ece0c8618682f06b61afce6dc34787e1bea62 Mon Sep 17 00:00:00 2001 From: David Ashby Date: Sat, 30 Dec 2023 20:27:24 -0500 Subject: [PATCH 01/10] initial sketch of how this will work --- .gitignore | 5 +- Makefile | 16 +- cmd/{manage => cli}/events.go | 0 cmd/{manage => cli}/main.go | 0 cmd/serve/main.go | 114 ++++++++++---- config/config.go | 1 + frontend/admin/index.html | 5 + frontend/frontend.go | 4 + go.mod | 90 ++++++++++- go.sum | 283 ++++++++++++++++++++++++++++++++-- 10 files changed, 462 insertions(+), 56 deletions(-) rename cmd/{manage => cli}/events.go (100%) rename cmd/{manage => cli}/main.go (100%) create mode 100644 frontend/admin/index.html diff --git a/.gitignore b/.gitignore index 0742ee2..b436743 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /server -/manager +/cli *.properties .DS_Store /vendor -.recordsCache \ No newline at end of file +.recordsCache +.config \ No newline at end of file diff --git a/Makefile b/Makefile index 178b88d..ae65042 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: up down run-server run-manager test +.PHONY: up down run-server run-cli test GOFILES=$(shell find . -name '*.go' -o -name 'go.*') STATICFILES=$(shell find . -name '*.js' -o -name '*.css' -o -name '*.html') @@ -9,26 +9,26 @@ include local.properties export endif -build: server manager +build: server cli run-server: build ./server -run-manager: build - ./manager +run-cli: build + ./cli server: $(GOFILES) $(STATICFILES) go build -o server ./cmd/serve -manager: $(GOFILES) $(SQLFILES) - go build -o manager ./cmd/manage +cli: $(GOFILES) $(SQLFILES) + go build -o cli ./cmd/cli test: go test ./... -cover # dev dependencies up: - docker compose up -d + docker-compose up -d down: - docker compose down + docker-compose down diff --git a/cmd/manage/events.go b/cmd/cli/events.go similarity index 100% rename from cmd/manage/events.go rename to cmd/cli/events.go diff --git a/cmd/manage/main.go b/cmd/cli/main.go similarity index 100% rename from cmd/manage/main.go rename to cmd/cli/main.go diff --git a/cmd/serve/main.go b/cmd/serve/main.go index 1f71c5b..8868721 100644 --- a/cmd/serve/main.go +++ b/cmd/serve/main.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "fmt" "io/fs" "log" "net/http" @@ -14,15 +15,11 @@ import ( "git.yetaga.in/alazyreader/library/frontend" "git.yetaga.in/alazyreader/library/media" "github.com/kelseyhightower/envconfig" + "golang.org/x/sync/errgroup" + "tailscale.com/client/tailscale" + "tailscale.com/tsnet" ) -func max(a, b int) int { - if a > b { - return a - } - return b -} - func obscureStr(in string, l int) string { return in[0:max(l, len(in))] + strings.Repeat("*", max(0, len(in)-l)) } @@ -41,6 +38,12 @@ type Router struct { rcol RecordCollection } +type AdminRouter struct { + static fs.FS + lib Library + ts *tailscale.LocalClient +} + func writeJSON(w http.ResponseWriter, b []byte, status int) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) @@ -60,6 +63,12 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { StaticHandler(r.static).ServeHTTP(w, req) } +func (r *AdminRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { + whois, _ := r.ts.WhoIs(req.Context(), req.RemoteAddr) + w.Write([]byte(fmt.Sprintf("%+v", whois.UserProfile.DisplayName))) + // StaticHandler(r.static).ServeHTTP(w, req) +} + func BooksAPIHandler(l Library) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { books, err := l.GetAllBooks(r.Context()) @@ -107,37 +116,76 @@ func main() { if err != nil { log.Fatalln(err) } - if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" { - if c.DBPass != "" { - c.DBPass = obscureStr(c.DBPass, 3) + var lib Library + if c.DBType == "memory" { + lib = &database.Memory{} + } else if c.DBType == "sql" { + if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" { + if c.DBPass != "" { + c.DBPass = obscureStr(c.DBPass, 3) + } + if c.DiscogsToken != "" { + c.DiscogsToken = obscureStr(c.DiscogsToken, 3) + } + log.Fatalf("vars: %+v", c) } - if c.DiscogsToken != "" { - c.DiscogsToken = obscureStr(c.DiscogsToken, 3) + sql, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName) + if err != nil { + log.Fatalln(err) } - log.Fatalf("vars: %+v", c) + err = sql.PrepareDatabase(context.Background()) + if err != nil { + log.Fatalln(err) + } + latest, run, err := sql.RunMigrations(context.Background()) + if err != nil { + log.Fatalln(err) + } + log.Printf("latest migration: %d; migrations run: %d", latest, run) + lib = sql } - lib, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName) - if err != nil { - log.Fatalln(err) - } - err = lib.PrepareDatabase(context.Background()) - if err != nil { - log.Fatalln(err) - } - latest, run, err := lib.RunMigrations(context.Background()) - if err != nil { - log.Fatalln(err) - } - log.Printf("latest migration: %d; migrations run: %d", latest, run) discogsCache, err := database.NewDiscogsCache(c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile) if err != nil { log.Fatalln(err) } - r := &Router{ - static: f, - lib: lib, - rcol: discogsCache, - } - log.Println("Listening on http://0.0.0.0:8080/") - log.Fatalln(http.ListenAndServe(":8080", r)) + errGroup := errgroup.Group{} + errGroup.Go(func() error { + return publicListener(8080, &Router{ + static: f, + lib: lib, + rcol: discogsCache, + }) + }) + errGroup.Go(func() error { + f, _ := frontend.AdminRoot() + return tailscaleListener("library-admin", &AdminRouter{ + static: f, + lib: lib, + }) + }) + log.Println(errGroup.Wait()) +} + +func publicListener(port int, handler http.Handler) error { + log.Printf("Listening on http://0.0.0.0:%d/", port) + return http.ListenAndServe(fmt.Sprintf(":%d", port), handler) +} + +func tailscaleListener(hostname string, handler *AdminRouter) error { + s := &tsnet.Server{ + Dir: ".config/" + hostname, + Hostname: hostname, + } + defer s.Close() + + ln, err := s.Listen("tcp", ":80") + if err != nil { + fmt.Printf("%+v\n", err) + return nil + } + handler.ts, err = s.LocalClient() + if err != nil { + return err + } + return (&http.Server{Handler: handler}).Serve(ln) } diff --git a/config/config.go b/config/config.go index b88c747..6a67ef4 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,7 @@ package config type Config struct { + DBType string `default:"sql"` DBUser string DBPass string DBHost string diff --git a/frontend/admin/index.html b/frontend/admin/index.html new file mode 100644 index 0000000..d3e2931 --- /dev/null +++ b/frontend/admin/index.html @@ -0,0 +1,5 @@ + + + hello world + + \ No newline at end of file diff --git a/frontend/frontend.go b/frontend/frontend.go index 49c6b61..5693779 100644 --- a/frontend/frontend.go +++ b/frontend/frontend.go @@ -11,3 +11,7 @@ var static embed.FS func Root() (fs.FS, error) { return fs.Sub(static, "files") } + +func AdminRoot() (fs.FS, error) { + return fs.Sub(static, "admin") +} diff --git a/go.mod b/go.mod index 9e154dc..f4ee110 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,98 @@ module git.yetaga.in/alazyreader/library -go 1.16 +go 1.21 + +toolchain go1.21.5 require ( github.com/gdamore/tcell/v2 v2.7.0 github.com/go-sql-driver/mysql v1.7.1 github.com/irlndts/go-discogs v0.3.6 github.com/kelseyhightower/envconfig v1.4.0 + golang.org/x/sync v0.5.0 + tailscale.com v1.56.1 +) + +require ( + filippo.io/edwards25519 v1.0.0 // indirect + github.com/akutz/memconn v0.1.0 // indirect + github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect + github.com/aws/aws-sdk-go-v2 v1.21.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.18.42 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.40 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect + github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect + github.com/aws/smithy-go v1.14.2 // indirect + github.com/coreos/go-iptables v0.7.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077 // indirect + github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect + github.com/fxamacker/cbor/v2 v2.5.0 // indirect + github.com/gdamore/encoding v1.0.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/gorilla/csrf v1.7.1 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/hdevalence/ed25519consensus v0.1.0 // indirect + github.com/illarion/gonotify v1.0.1 // indirect + github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect + github.com/jsimonetti/rtnetlink v1.3.5 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mdlayher/genetlink v1.3.2 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/sdnotify v1.0.0 // indirect + github.com/mdlayher/socket v0.5.0 // indirect + github.com/miekg/dns v1.1.56 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/safchain/ethtool v0.3.0 // indirect + github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect + github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect + github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e // indirect + github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect + github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect + github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect + github.com/tailscale/web-client-prebuilt v0.0.0-20231213172531-a4fa669015b2 // indirect + github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 // indirect + github.com/tcnksm/go-httpstat v0.2.0 // indirect + github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect + github.com/vishvananda/netlink v1.2.1-beta.2 // indirect + github.com/vishvananda/netns v0.0.4 // indirect + github.com/x448/float16 v0.8.4 // indirect + go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect + go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.15.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect + gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c // indirect + inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect + nhooyr.io/websocket v1.8.7 // indirect ) diff --git a/go.sum b/go.sum index 427873e..06c6f37 100644 --- a/go.sum +++ b/go.sum @@ -1,65 +1,324 @@ +filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc= +filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= +github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= +github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= +github.com/aws/aws-sdk-go-v2/config v1.18.42 h1:28jHROB27xZwU0CB88giDSjz7M1Sba3olb5JBGwina8= +github.com/aws/aws-sdk-go-v2/config v1.18.42/go.mod h1:4AZM3nMMxwlG+eZlxvBKqwVbkDLlnN2a4UGTL6HjaZI= +github.com/aws/aws-sdk-go-v2/credentials v1.13.40 h1:s8yOkDh+5b1jUDhMBtngF6zKWLDs84chUk2Vk0c38Og= +github.com/aws/aws-sdk-go-v2/credentials v1.13.40/go.mod h1:VtEHVAAqDWASwdOqj/1huyT6uHbs5s8FUHfDQdky/Rs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 h1:g+qlObJH4Kn4n21g69DjspU0hKTjWtq7naZ9OLCv0ew= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o= +github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0 h1:JON9MBvwUlM8HXylfB2caZuH3VXz9RxO4SMp2+TNc3Q= +github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0/go.mod h1:JjBzoceyKkpQY3v1GPIdg6kHqUFHRJ7SDlwtwoH0Qh8= +github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 h1:YkNzx1RLS0F5qdf9v1Q8Cuv9NXCL2TkosOxhzlUPV64= +github.com/aws/aws-sdk-go-v2/service/sso v1.14.1/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 h1:8lKOidPkmSmfUtiTgtdXWgaKItCZ/g75/jEk6Ql6GsA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4= +github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk= +github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU= +github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= +github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= +github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= +github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= +github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077 h1:WphxHslVftszsr0oZOHPaOjpmN/BsgNYF+gW/hxZXXc= +github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs= +github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= +github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= -github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= github.com/gdamore/tcell/v2 v2.7.0 h1:I5LiGTQuwrysAt1KS9wg1yFfOI3arI3ucFrxtd/xqaA= github.com/gdamore/tcell/v2 v2.7.0/go.mod h1:hl/KtAANGBecfIPxk+FzKvThTqI84oplgbPEmVX60b8= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= +github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= +github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c h1:06RMfw+TMMHtRuUOroMeatRCCgSMWXCJQeABvHU69YQ= +github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c/go.mod h1:BVIYo3cdnT4qSylnYqcd5YtmXhr51cJPGtnLBe/uLBU= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE= +github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU= +github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= +github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= +github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ= +github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs= github.com/irlndts/go-discogs v0.3.6 h1:3oIJEkLGQ1ffJcoo6wvtawPI4/SyHoRpnu25Y51U4wg= github.com/irlndts/go-discogs v0.3.6/go.mod h1:UVQ05FdCzH4P/usnSxQDh77QYE37HvmPnSCgogioljo= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= +github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA= +github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= +github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= +github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= +github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= +github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= +github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= +github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= +github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= +github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= +github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= +github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e h1:JyeJF/HuSwvxWtsR1c0oKX1lzaSH5Wh4aX+MgiStaGQ= +github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e/go.mod h1:DjoeCULdP6vTJ/xY+nzzR9LaUHprkbZEpNidX0aqEEk= +github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= +github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= +github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= +github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= +github.com/tailscale/web-client-prebuilt v0.0.0-20231213172531-a4fa669015b2 h1:lR1voET3dwe3CxacGAiva4k08TXtQ6Dlmult4JILlj4= +github.com/tailscale/web-client-prebuilt v0.0.0-20231213172531-a4fa669015b2/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 h1:zwsem4CaamMdC3tFoTpzrsUSMDPV0K6rhnQdF7kXekQ= +github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= +github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= +github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= +github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8= +github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY= +github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg= +github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= +github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= +go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= +go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ= +go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 h1:j3D9DvWRpUfIyFfDPws7LoIZ2MAI1OJHdQXtTnYtN+k= +golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c h1:bYb98Ra11fJ8F2xFbZx0zg2VQ28lYqC1JxfaaF53xqY= +gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= +honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= +honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg= +inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU= +inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q= +inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +software.sslmate.com/src/go-pkcs12 v0.2.1 h1:tbT1jjaeFOF230tzOIRJ6U5S1jNqpsSyNjzDd58H3J8= +software.sslmate.com/src/go-pkcs12 v0.2.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= +tailscale.com v1.56.1 h1:V3HBDJai3u7xo22Xlv7ioqKNZQdxOJebLYCNqCXVwZg= +tailscale.com v1.56.1/go.mod h1:XQk6fCN8oMJ+qbCmW+2WS/VM3jTA9nIHT6O19t0hZeQ= -- 2.45.2 From 034232f866ef61c0630566aa525adbd2b569d3ff Mon Sep 17 00:00:00 2001 From: David Ashby Date: Mon, 1 Jan 2024 16:26:36 -0500 Subject: [PATCH 02/10] refactor refactor refactor --- cmd/serve/api.go | 120 +++++++++++++++++++++++++ cmd/serve/main.go | 224 +++++++++++++++++++--------------------------- 2 files changed, 214 insertions(+), 130 deletions(-) create mode 100644 cmd/serve/api.go diff --git a/cmd/serve/api.go b/cmd/serve/api.go new file mode 100644 index 0000000..7560050 --- /dev/null +++ b/cmd/serve/api.go @@ -0,0 +1,120 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/fs" + "net/http" + + "git.yetaga.in/alazyreader/library/media" + "tailscale.com/client/tailscale" + "tailscale.com/util/must" +) + +type Router struct { + static fs.FS + lib Library + rcol RecordCollection +} + +type AdminRouter struct { + static fs.FS + lib Library + ts *tailscale.LocalClient +} + +func writeError[T any](w http.ResponseWriter) func(t T, err error) (T, bool) { + return func(t T, err error) (T, bool) { + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return t, err != nil + } +} + +func writeJSON(w http.ResponseWriter, b []byte, status int) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(status) + w.Write(b) + w.Write([]byte("\n")) +} + +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/api/records" { + RecordsAPIHandler(r.rcol).ServeHTTP(w, req) + return + } + if req.URL.Path == "/api/books" { + BooksAPIHandler(r.lib).ServeHTTP(w, req) + return + } + StaticHandler(r.static).ServeHTTP(w, req) +} + +func (r *AdminRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { + whois, _ := r.ts.WhoIs(req.Context(), req.RemoteAddr) + switch req.URL.Path { + case "/api/books": + switch req.Method { + case http.MethodGet: + books, ok := writeError[[]media.Book](w)(r.lib.GetAllBooks(req.Context())) + if !ok { + return + } + b, ok := writeError[[]byte](w)(json.Marshal(books)) + if !ok { + return + } + writeJSON(w, b, http.StatusOK) + case http.MethodPost: + default: + badMethod(w) + } + return + } + w.Write([]byte(fmt.Sprintf("%+v", whois.UserProfile.DisplayName))) + // StaticHandler(r.static).ServeHTTP(w, req) +} + +func badMethod(w http.ResponseWriter) { + writeJSON(w, + must.Get(json.Marshal(struct{ Error string }{Error: "method not supported"})), + http.StatusMethodNotAllowed) +} + +func BooksAPIHandler(l Library) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + books, err := l.GetAllBooks(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + b, err := json.Marshal(books) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + + } + writeJSON(w, b, http.StatusOK) + }) +} + +func RecordsAPIHandler(l RecordCollection) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + books, err := l.GetAllRecords(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + b, err := json.Marshal(books) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + writeJSON(w, b, http.StatusOK) + }) +} + +func StaticHandler(f fs.FS) http.Handler { + return http.FileServer(http.FS(f)) +} diff --git a/cmd/serve/main.go b/cmd/serve/main.go index 8868721..970a63b 100644 --- a/cmd/serve/main.go +++ b/cmd/serve/main.go @@ -2,11 +2,12 @@ package main import ( "context" - "encoding/json" "fmt" - "io/fs" "log" + "net" "net/http" + "os" + "os/signal" "strings" "time" @@ -16,8 +17,8 @@ import ( "git.yetaga.in/alazyreader/library/media" "github.com/kelseyhightower/envconfig" "golang.org/x/sync/errgroup" - "tailscale.com/client/tailscale" "tailscale.com/tsnet" + "tailscale.com/util/must" ) func obscureStr(in string, l int) string { @@ -32,160 +33,123 @@ type RecordCollection interface { GetAllRecords(context.Context) ([]media.Record, error) } -type Router struct { - static fs.FS - lib Library - rcol RecordCollection -} - -type AdminRouter struct { - static fs.FS - lib Library - ts *tailscale.LocalClient -} - -func writeJSON(w http.ResponseWriter, b []byte, status int) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(status) - w.Write(b) - w.Write([]byte("\n")) -} - -func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if req.URL.Path == "/api/records" { - RecordsAPIHandler(r.rcol).ServeHTTP(w, req) - return - } - if req.URL.Path == "/api/books" { - BooksAPIHandler(r.lib).ServeHTTP(w, req) - return - } - StaticHandler(r.static).ServeHTTP(w, req) -} - -func (r *AdminRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { - whois, _ := r.ts.WhoIs(req.Context(), req.RemoteAddr) - w.Write([]byte(fmt.Sprintf("%+v", whois.UserProfile.DisplayName))) - // StaticHandler(r.static).ServeHTTP(w, req) -} - -func BooksAPIHandler(l Library) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - books, err := l.GetAllBooks(r.Context()) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - b, err := json.Marshal(books) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - - } - writeJSON(w, b, http.StatusOK) - }) -} - -func RecordsAPIHandler(l RecordCollection) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - books, err := l.GetAllRecords(r.Context()) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - b, err := json.Marshal(books) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - writeJSON(w, b, http.StatusOK) - }) -} - -func StaticHandler(f fs.FS) http.Handler { - return http.FileServer(http.FS(f)) -} - func main() { var c config.Config - err := envconfig.Process("library", &c) - if err != nil { - log.Fatalln(err) - } - f, err := frontend.Root() - if err != nil { - log.Fatalln(err) - } + must.Do(envconfig.Process("library", &c)) + var lib Library + var err error if c.DBType == "memory" { lib = &database.Memory{} } else if c.DBType == "sql" { - if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" { - if c.DBPass != "" { - c.DBPass = obscureStr(c.DBPass, 3) - } - if c.DiscogsToken != "" { - c.DiscogsToken = obscureStr(c.DiscogsToken, 3) - } - log.Fatalf("vars: %+v", c) - } - sql, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName) + var latest, run int + lib, latest, run, err = setupSQL(c) if err != nil { - log.Fatalln(err) - } - err = sql.PrepareDatabase(context.Background()) - if err != nil { - log.Fatalln(err) - } - latest, run, err := sql.RunMigrations(context.Background()) - if err != nil { - log.Fatalln(err) + log.Fatalf("err starting sql connection: %v", err) } log.Printf("latest migration: %d; migrations run: %d", latest, run) - lib = sql - } - discogsCache, err := database.NewDiscogsCache(c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile) - if err != nil { - log.Fatalln(err) } + discogsCache := must.Get(database.NewDiscogsCache( + c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile, + )) + + frontendRoot := must.Get(frontend.Root()) + adminRoot := must.Get(frontend.AdminRoot()) + + servers := make(chan (*http.Server), 2) errGroup := errgroup.Group{} - errGroup.Go(func() error { - return publicListener(8080, &Router{ - static: f, + errGroup.Go(start(servers)( + publicServer(8080, &Router{ + static: frontendRoot, lib: lib, rcol: discogsCache, - }) - }) - errGroup.Go(func() error { - f, _ := frontend.AdminRoot() - return tailscaleListener("library-admin", &AdminRouter{ - static: f, + }))) + errGroup.Go(start(servers)( + tailscaleListener("library-admin", &AdminRouter{ + static: adminRoot, lib: lib, - }) - }) + }))) + errGroup.Go(shutdown(servers)) + log.Println(errGroup.Wait()) } -func publicListener(port int, handler http.Handler) error { - log.Printf("Listening on http://0.0.0.0:%d/", port) - return http.ListenAndServe(fmt.Sprintf(":%d", port), handler) +func setupSQL(c config.Config) (Library, int, int, error) { + if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" { + if c.DBPass != "" { + c.DBPass = obscureStr(c.DBPass, 3) + } + if c.DiscogsToken != "" { + c.DiscogsToken = obscureStr(c.DiscogsToken, 3) + } + return nil, 0, 0, fmt.Errorf("invalid config; vars provided: %+v", c) + } + sql, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName) + if err != nil { + return nil, 0, 0, err + } + err = sql.PrepareDatabase(context.Background()) + if err != nil { + return nil, 0, 0, err + } + latest, run, err := sql.RunMigrations(context.Background()) + if err != nil { + return nil, 0, 0, err + } + return sql, latest, run, nil } -func tailscaleListener(hostname string, handler *AdminRouter) error { +func errFunc(err error) func() error { + return func() error { return err } +} + +func start(servers chan (*http.Server)) func(*http.Server, net.Listener, error) func() error { + return func(s *http.Server, l net.Listener, err error) func() error { + if err != nil { + return errFunc(err) + } + servers <- s + return errFunc(s.Serve(l)) + } +} + +func shutdown(servers chan (*http.Server)) func() error { + sigint := make(chan os.Signal, 1) + signal.Notify(sigint, os.Interrupt) + <-sigint + close(servers) + for server := range servers { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + if err := server.Shutdown(ctx); err != nil { + log.Panicf("error during shutdown: %v", err) + } + cancel() + } + return errFunc(nil) +} + +func publicServer(port int, handler http.Handler) (*http.Server, net.Listener, error) { + server := &http.Server{Handler: handler} + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", 8080)) + if err != nil { + return nil, nil, err + } + return server, ln, nil +} + +func tailscaleListener(hostname string, handler *AdminRouter) (*http.Server, net.Listener, error) { s := &tsnet.Server{ Dir: ".config/" + hostname, Hostname: hostname, } - defer s.Close() - ln, err := s.Listen("tcp", ":80") if err != nil { - fmt.Printf("%+v\n", err) - return nil + return nil, nil, err } handler.ts, err = s.LocalClient() if err != nil { - return err + return nil, nil, err } - return (&http.Server{Handler: handler}).Serve(ln) + server := &http.Server{Handler: handler} + return server, ln, nil } -- 2.45.2 From fa733641077253c279bc5057d0591f8d18c0ea1b Mon Sep 17 00:00:00 2001 From: David Ashby Date: Mon, 1 Jan 2024 17:31:18 -0500 Subject: [PATCH 03/10] start adding method handlers --- cmd/serve/api.go | 212 +++++++++++++++++++++++++++++++--------------- cmd/serve/main.go | 56 ++++++------ database/mysql.go | 80 +++++------------ 3 files changed, 195 insertions(+), 153 deletions(-) diff --git a/cmd/serve/api.go b/cmd/serve/api.go index 7560050..d79cca3 100644 --- a/cmd/serve/api.go +++ b/cmd/serve/api.go @@ -2,13 +2,12 @@ package main import ( "encoding/json" - "fmt" + "io" "io/fs" "net/http" "git.yetaga.in/alazyreader/library/media" "tailscale.com/client/tailscale" - "tailscale.com/util/must" ) type Router struct { @@ -23,98 +22,175 @@ type AdminRouter struct { ts *tailscale.LocalClient } -func writeError[T any](w http.ResponseWriter) func(t T, err error) (T, bool) { - return func(t T, err error) (T, bool) { +type handler struct { + get func() + post func() + put func() + delete func() +} + +func (h handler) Handle(w http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodHead && h.get != nil { + h.get() + } else if req.Method == http.MethodGet && h.get != nil { + h.get() + } else if req.Method == http.MethodPost && h.post != nil { + h.post() + } else if req.Method == http.MethodPut && h.put != nil { + h.put() + } else if req.Method == http.MethodDelete && h.delete != nil { + h.delete() + } else { + badMethod(w) + } +} + +func writeError[T any](t T, err error) func(w http.ResponseWriter) (T, bool) { + return func(w http.ResponseWriter) (T, bool) { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } - return t, err != nil + return t, err == nil } } -func writeJSON(w http.ResponseWriter, b []byte, status int) { +func writeNoBody(w http.ResponseWriter, status int) { + w.WriteHeader(status) +} +func writeJSON(w http.ResponseWriter, b any, status int) { + bytes, err := json.Marshal(b) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) - w.Write(b) + w.Write(bytes) w.Write([]byte("\n")) } -func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if req.URL.Path == "/api/records" { - RecordsAPIHandler(r.rcol).ServeHTTP(w, req) - return +func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/records": + handler{ + get: func() { getRecords(router.rcol, w, r) }, + }.Handle(w, r) + case "/api/books": + handler{ + get: func() { getBooks(router.lib, w, r) }, + }.Handle(w, r) + default: + static(router.static).ServeHTTP(w, r) } - if req.URL.Path == "/api/books" { - BooksAPIHandler(r.lib).ServeHTTP(w, req) - return - } - StaticHandler(r.static).ServeHTTP(w, req) } -func (r *AdminRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { - whois, _ := r.ts.WhoIs(req.Context(), req.RemoteAddr) - switch req.URL.Path { +func (router *AdminRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/whoami": + handler{ + get: func() { getWhoAmI(router.ts, w, r) }, + }.Handle(w, r) case "/api/books": - switch req.Method { - case http.MethodGet: - books, ok := writeError[[]media.Book](w)(r.lib.GetAllBooks(req.Context())) - if !ok { - return - } - b, ok := writeError[[]byte](w)(json.Marshal(books)) - if !ok { - return - } - writeJSON(w, b, http.StatusOK) - case http.MethodPost: - default: - badMethod(w) - } - return + handler{ + get: func() { getBooks(router.lib, w, r) }, + post: func() { addBook(router.lib, w, r) }, + delete: func() { deleteBook(router.lib, w, r) }, + }.Handle(w, r) + default: + static(router.static).ServeHTTP(w, r) } - w.Write([]byte(fmt.Sprintf("%+v", whois.UserProfile.DisplayName))) - // StaticHandler(r.static).ServeHTTP(w, req) } func badMethod(w http.ResponseWriter) { writeJSON(w, - must.Get(json.Marshal(struct{ Error string }{Error: "method not supported"})), + struct{ Error string }{Error: "method not supported"}, http.StatusMethodNotAllowed) } -func BooksAPIHandler(l Library) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - books, err := l.GetAllBooks(r.Context()) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - b, err := json.Marshal(books) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - - } - writeJSON(w, b, http.StatusOK) - }) +func getBooks(l Library, w http.ResponseWriter, r *http.Request) { + books, err := l.GetAllBooks(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + writeJSON(w, books, http.StatusOK) } -func RecordsAPIHandler(l RecordCollection) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - books, err := l.GetAllRecords(r.Context()) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - b, err := json.Marshal(books) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - writeJSON(w, b, http.StatusOK) - }) +func addBook(l Library, w http.ResponseWriter, r *http.Request) { + if r.Body == nil { + http.Error(w, "no body provided", http.StatusBadRequest) + return + } + defer r.Body.Close() + b, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "error reading body", http.StatusBadRequest) + return + } + book := &media.Book{} + err = json.Unmarshal(b, book) + if err != nil { + http.Error(w, "error parsing body", http.StatusBadRequest) + return + } + err = l.AddBook(r.Context(), book) + if err != nil { + http.Error(w, "error parsing body", http.StatusBadRequest) + return + } + writeNoBody(w, http.StatusAccepted) } -func StaticHandler(f fs.FS) http.Handler { +func deleteBook(l Library, w http.ResponseWriter, r *http.Request) { + if r.Body == nil { + http.Error(w, "no body provided", http.StatusBadRequest) + return + } + defer r.Body.Close() + b, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "error reading body", http.StatusBadRequest) + return + } + book := &media.Book{} + err = json.Unmarshal(b, book) + if err != nil { + http.Error(w, "error parsing body", http.StatusBadRequest) + return + } + err = l.DeleteBook(r.Context(), book) + if err != nil { + http.Error(w, "error parsing body", http.StatusBadRequest) + return + } + writeNoBody(w, http.StatusAccepted) +} + +func getRecords(l RecordCollection, w http.ResponseWriter, r *http.Request) { + records, err := l.GetAllRecords(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + writeJSON(w, records, http.StatusOK) +} + +func getWhoAmI(ts *tailscale.LocalClient, w http.ResponseWriter, r *http.Request) { + whois, ok := writeError(ts.WhoIs(r.Context(), r.RemoteAddr))(w) + if !ok { + return + } + writeJSON(w, struct { + Username string `json:"Username"` + DisplayName string `json:"DisplayName"` + ProfilePicURL string `json:"ProfilePicURL"` + }{ + Username: whois.UserProfile.LoginName, + DisplayName: whois.UserProfile.DisplayName, + ProfilePicURL: whois.UserProfile.ProfilePicURL, + }, http.StatusOK) +} + +func static(f fs.FS) http.Handler { return http.FileServer(http.FS(f)) } diff --git a/cmd/serve/main.go b/cmd/serve/main.go index 970a63b..267a23d 100644 --- a/cmd/serve/main.go +++ b/cmd/serve/main.go @@ -27,6 +27,8 @@ func obscureStr(in string, l int) string { type Library interface { GetAllBooks(context.Context) ([]media.Book, error) + AddBook(context.Context, *media.Book) error + DeleteBook(context.Context, *media.Book) error } type RecordCollection interface { @@ -56,20 +58,26 @@ func main() { frontendRoot := must.Get(frontend.Root()) adminRoot := must.Get(frontend.AdminRoot()) - servers := make(chan (*http.Server), 2) + servers := make(chan (*http.Server), 3) errGroup := errgroup.Group{} - errGroup.Go(start(servers)( - publicServer(8080, &Router{ - static: frontendRoot, - lib: lib, - rcol: discogsCache, - }))) - errGroup.Go(start(servers)( - tailscaleListener("library-admin", &AdminRouter{ - static: adminRoot, - lib: lib, - }))) - errGroup.Go(shutdown(servers)) + errGroup.Go(func() error { + return start(servers)( + publicServer(8080, &Router{ + static: frontendRoot, + lib: lib, + rcol: discogsCache, + })) + }) + errGroup.Go(func() error { + return start(servers)( + tailscaleListener("library-admin", &AdminRouter{ + static: adminRoot, + lib: lib, + })) + }) + errGroup.Go(func() error { + return shutdown(servers) + }) log.Println(errGroup.Wait()) } @@ -99,33 +107,30 @@ func setupSQL(c config.Config) (Library, int, int, error) { return sql, latest, run, nil } -func errFunc(err error) func() error { - return func() error { return err } -} - -func start(servers chan (*http.Server)) func(*http.Server, net.Listener, error) func() error { - return func(s *http.Server, l net.Listener, err error) func() error { +func start(servers chan (*http.Server)) func(*http.Server, net.Listener, error) error { + return func(s *http.Server, l net.Listener, err error) error { if err != nil { - return errFunc(err) + return err } servers <- s - return errFunc(s.Serve(l)) + return s.Serve(l) } } -func shutdown(servers chan (*http.Server)) func() error { +func shutdown(servers chan (*http.Server)) error { sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) <-sigint close(servers) + var err error for server := range servers { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - if err := server.Shutdown(ctx); err != nil { - log.Panicf("error during shutdown: %v", err) + if shutdownerr := server.Shutdown(ctx); shutdownerr != nil { + err = shutdownerr } cancel() } - return errFunc(nil) + return err } func publicServer(port int, handler http.Handler) (*http.Server, net.Listener, error) { @@ -134,6 +139,7 @@ func publicServer(port int, handler http.Handler) (*http.Server, net.Listener, e if err != nil { return nil, nil, err } + log.Println("starting public server") return server, ln, nil } diff --git a/database/mysql.go b/database/mysql.go index 93df8fd..cc30ee9 100644 --- a/database/mysql.go +++ b/database/mysql.go @@ -153,9 +153,8 @@ func (m *MySQL) GetAllBooks(ctx context.Context) ([]media.Book, error) { } allBooksQuery := fmt.Sprintf(`SELECT - id, title, authors, sortauthor, isbn10, isbn13, format, - genre, publisher, series, volume, year, signed, - description, notes, coverurl, childrens + id, title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, + series, volume, year, signed, description, notes, coverurl, childrens FROM %s`, m.tableName) books := []media.Book{} @@ -169,12 +168,8 @@ func (m *MySQL) GetAllBooks(ctx context.Context) ([]media.Book, error) { b := media.Book{} var authors string err := rows.Scan( - &b.ID, &b.Title, &authors, - &b.SortAuthor, &b.ISBN10, &b.ISBN13, - &b.Format, &b.Genre, &b.Publisher, - &b.Series, &b.Volume, &b.Year, - &b.Signed, &b.Description, &b.Notes, - &b.CoverURL, &b.Childrens) + &b.ID, &b.Title, &authors, &b.SortAuthor, &b.ISBN10, &b.ISBN13, &b.Format, &b.Genre, &b.Publisher, + &b.Series, &b.Volume, &b.Year, &b.Signed, &b.Description, &b.Notes, &b.CoverURL, &b.Childrens) if err != nil { return nil, err } @@ -193,25 +188,14 @@ func (m *MySQL) AddBook(ctx context.Context, b *media.Book) error { res, err := m.connection.ExecContext(ctx, ` INSERT INTO `+m.tableName+` - (title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, series, volume, year, signed, description, notes, coverurl, childrens) + ( + title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, series, + volume, year, signed, description, notes, coverurl, childrens + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - b.Title, - strings.Join(b.Authors, ";"), - b.SortAuthor, - b.ISBN10, - b.ISBN13, - b.Format, - b.Genre, - b.Publisher, - b.Series, - b.Volume, - b.Year, - b.Signed, - b.Description, - b.Notes, - b.CoverURL, - b.Childrens, + b.Title, strings.Join(b.Authors, ";"), b.SortAuthor, b.ISBN10, b.ISBN13, b.Format, b.Genre, b.Publisher, b.Series, + b.Volume, b.Year, b.Signed, b.Description, b.Notes, b.CoverURL, b.Childrens, ) if err != nil { return err @@ -236,41 +220,13 @@ func (m *MySQL) UpdateBook(ctx context.Context, old, new *media.Book) error { res, err := m.connection.ExecContext(ctx, ` UPDATE `+m.tableName+` - SET id=? - title=? - authors=? - sortauthor=? - isbn10=? - isbn13=? - format=? - genre=? - publisher=? - series=? - volume=? - year=? - signed=? - description=? - notes=? - coverurl=? - childrens=? + SET + id=? title=? authors=? sortauthor=? isbn10=? isbn13=? format=? genre=? publisher=? + series=? volume=? year=? signed=? description=? notes=? coverurl=? childrens=? WHERE id=?`, - new.Title, - strings.Join(new.Authors, ";"), - new.SortAuthor, - new.ISBN10, - new.ISBN13, - new.Format, - new.Genre, - new.Publisher, - new.Series, - new.Volume, - new.Year, - new.Signed, - new.Description, - new.Notes, - new.CoverURL, - new.Childrens, - old.ID) + new.Title, strings.Join(new.Authors, ";"), new.SortAuthor, new.ISBN10, new.ISBN13, new.Format, new.Genre, new.Publisher, + new.Series, new.Volume, new.Year, new.Signed, new.Description, new.Notes, new.CoverURL, new.Childrens, old.ID, + ) if err != nil { return err } @@ -284,6 +240,10 @@ func (m *MySQL) UpdateBook(ctx context.Context, old, new *media.Book) error { return nil } +func (m *MySQL) DeleteBook(_ context.Context, b *media.Book) error { + return nil +} + func parseMigrationFileName(filename string) (int, string, error) { sp := strings.SplitN(filename, "-", 2) i, err := strconv.Atoi(sp[0]) -- 2.45.2 From e62006f4e1f87dff9225cf0f2a0745a49953e461 Mon Sep 17 00:00:00 2001 From: David Ashby Date: Mon, 1 Jan 2024 17:52:11 -0500 Subject: [PATCH 04/10] more refactoring --- cmd/serve/api.go | 57 +++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/cmd/serve/api.go b/cmd/serve/api.go index d79cca3..516748c 100644 --- a/cmd/serve/api.go +++ b/cmd/serve/api.go @@ -45,22 +45,14 @@ func (h handler) Handle(w http.ResponseWriter, req *http.Request) { } } -func writeError[T any](t T, err error) func(w http.ResponseWriter) (T, bool) { - return func(w http.ResponseWriter) (T, bool) { - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - return t, err == nil - } +func writeJSONerror(w http.ResponseWriter, err string, status int) { + writeJSON(w, struct{ Status, Reason string }{Status: "error", Reason: err}, status) } -func writeNoBody(w http.ResponseWriter, status int) { - w.WriteHeader(status) -} func writeJSON(w http.ResponseWriter, b any, status int) { bytes, err := json.Marshal(b) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + writeJSONerror(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") @@ -102,15 +94,13 @@ func (router *AdminRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func badMethod(w http.ResponseWriter) { - writeJSON(w, - struct{ Error string }{Error: "method not supported"}, - http.StatusMethodNotAllowed) + writeJSONerror(w, "method not supported", http.StatusMethodNotAllowed) } func getBooks(l Library, w http.ResponseWriter, r *http.Request) { books, err := l.GetAllBooks(r.Context()) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + writeJSONerror(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, books, http.StatusOK) @@ -118,77 +108,70 @@ func getBooks(l Library, w http.ResponseWriter, r *http.Request) { func addBook(l Library, w http.ResponseWriter, r *http.Request) { if r.Body == nil { - http.Error(w, "no body provided", http.StatusBadRequest) + writeJSONerror(w, "no body provided", http.StatusBadRequest) return } defer r.Body.Close() b, err := io.ReadAll(r.Body) if err != nil { - http.Error(w, "error reading body", http.StatusBadRequest) + writeJSONerror(w, "error reading body", http.StatusBadRequest) return } book := &media.Book{} err = json.Unmarshal(b, book) if err != nil { - http.Error(w, "error parsing body", http.StatusBadRequest) + writeJSONerror(w, "error parsing body", http.StatusBadRequest) return } err = l.AddBook(r.Context(), book) if err != nil { - http.Error(w, "error parsing body", http.StatusBadRequest) + writeJSONerror(w, "error parsing body", http.StatusBadRequest) return } - writeNoBody(w, http.StatusAccepted) + w.WriteHeader(http.StatusAccepted) } func deleteBook(l Library, w http.ResponseWriter, r *http.Request) { if r.Body == nil { - http.Error(w, "no body provided", http.StatusBadRequest) + writeJSONerror(w, "no body provided", http.StatusBadRequest) return } defer r.Body.Close() b, err := io.ReadAll(r.Body) if err != nil { - http.Error(w, "error reading body", http.StatusBadRequest) + writeJSONerror(w, "error reading body", http.StatusBadRequest) return } book := &media.Book{} err = json.Unmarshal(b, book) if err != nil { - http.Error(w, "error parsing body", http.StatusBadRequest) + writeJSONerror(w, "error parsing body", http.StatusBadRequest) return } err = l.DeleteBook(r.Context(), book) if err != nil { - http.Error(w, "error parsing body", http.StatusBadRequest) + writeJSONerror(w, "error deleting book", http.StatusInternalServerError) return } - writeNoBody(w, http.StatusAccepted) + w.WriteHeader(http.StatusAccepted) } func getRecords(l RecordCollection, w http.ResponseWriter, r *http.Request) { records, err := l.GetAllRecords(r.Context()) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + writeJSONerror(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, records, http.StatusOK) } func getWhoAmI(ts *tailscale.LocalClient, w http.ResponseWriter, r *http.Request) { - whois, ok := writeError(ts.WhoIs(r.Context(), r.RemoteAddr))(w) - if !ok { + whois, err := ts.WhoIs(r.Context(), r.RemoteAddr) + if err != nil { + writeJSONerror(w, err.Error(), http.StatusInternalServerError) return } - writeJSON(w, struct { - Username string `json:"Username"` - DisplayName string `json:"DisplayName"` - ProfilePicURL string `json:"ProfilePicURL"` - }{ - Username: whois.UserProfile.LoginName, - DisplayName: whois.UserProfile.DisplayName, - ProfilePicURL: whois.UserProfile.ProfilePicURL, - }, http.StatusOK) + writeJSON(w, whois.UserProfile, http.StatusOK) } func static(f fs.FS) http.Handler { -- 2.45.2 From c2ac67aa0768ced8f23a8c56fde5145082842abc Mon Sep 17 00:00:00 2001 From: David Ashby Date: Mon, 1 Jan 2024 20:10:58 -0500 Subject: [PATCH 05/10] start building frontend --- cmd/serve/api.go | 35 ++--- frontend/admin/app.js | 217 ++++++++++++++++++++++++++++ frontend/admin/favicon.ico | Bin 0 -> 4414 bytes frontend/admin/favicon.png | Bin 0 -> 70262 bytes frontend/admin/index.html | 50 ++++++- frontend/admin/style.css | 284 +++++++++++++++++++++++++++++++++++++ frontend/frontend.go | 5 +- 7 files changed, 562 insertions(+), 29 deletions(-) create mode 100644 frontend/admin/app.js create mode 100644 frontend/admin/favicon.ico create mode 100644 frontend/admin/favicon.png create mode 100644 frontend/admin/style.css diff --git a/cmd/serve/api.go b/cmd/serve/api.go index 516748c..cdf3c8f 100644 --- a/cmd/serve/api.go +++ b/cmd/serve/api.go @@ -22,27 +22,14 @@ type AdminRouter struct { ts *tailscale.LocalClient } -type handler struct { - get func() - post func() - put func() - delete func() -} +type handler map[string]func() func (h handler) Handle(w http.ResponseWriter, req *http.Request) { - if req.Method == http.MethodHead && h.get != nil { - h.get() - } else if req.Method == http.MethodGet && h.get != nil { - h.get() - } else if req.Method == http.MethodPost && h.post != nil { - h.post() - } else if req.Method == http.MethodPut && h.put != nil { - h.put() - } else if req.Method == http.MethodDelete && h.delete != nil { - h.delete() - } else { - badMethod(w) + if f, ok := h[req.Method]; ok { + f() + return } + badMethod(w) } func writeJSONerror(w http.ResponseWriter, err string, status int) { @@ -65,11 +52,11 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/records": handler{ - get: func() { getRecords(router.rcol, w, r) }, + http.MethodGet: func() { getRecords(router.rcol, w, r) }, }.Handle(w, r) case "/api/books": handler{ - get: func() { getBooks(router.lib, w, r) }, + http.MethodGet: func() { getBooks(router.lib, w, r) }, }.Handle(w, r) default: static(router.static).ServeHTTP(w, r) @@ -80,13 +67,13 @@ func (router *AdminRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/whoami": handler{ - get: func() { getWhoAmI(router.ts, w, r) }, + http.MethodGet: func() { getWhoAmI(router.ts, w, r) }, }.Handle(w, r) case "/api/books": handler{ - get: func() { getBooks(router.lib, w, r) }, - post: func() { addBook(router.lib, w, r) }, - delete: func() { deleteBook(router.lib, w, r) }, + http.MethodGet: func() { getBooks(router.lib, w, r) }, + http.MethodPost: func() { addBook(router.lib, w, r) }, + http.MethodDelete: func() { deleteBook(router.lib, w, r) }, }.Handle(w, r) default: static(router.static).ServeHTTP(w, r) diff --git a/frontend/admin/app.js b/frontend/admin/app.js new file mode 100644 index 0000000..d8c2d4c --- /dev/null +++ b/frontend/admin/app.js @@ -0,0 +1,217 @@ +var sortState = { + sortBy: "sortAuthor", + sortOrder: "asc", +}; + +function init() { + fetch("/api/books") + .then((response) => response.json()) + .then((books) => { + // prepare response + books.forEach(apiResponseParsing); + document.getElementById("search").addEventListener("input", (e) => { + renderTable( + search( + books, + e.target.value, + document.getElementById("childrens").checked + ) + ); + }); + document.getElementById("childrens").addEventListener("change", (e) => { + renderTable( + search( + books, + document.getElementById("search").value, + e.target.checked + ) + ); + }); + renderTable( + search(books, "", document.getElementById("childrens").checked) + ); + }); +} + +function renderTable(books, sortField) { + if (sortField) { + if (sortState.sortBy === sortField && sortState.sortOrder === "asc") { + sortState.sortOrder = "desc"; + } else { + sortState.sortOrder = "asc"; + } + sortState.sortBy = sortField; + } + books.sort((one, two) => + (one[sortState.sortBy] + one["sortTitle"]).localeCompare( + two[sortState.sortBy] + two["sortTitle"] + ) + ); + if (sortState.sortOrder === "desc") { + books.reverse(); + } + books.forEach((e, i) => (e.rowNumber = i)); // re-key + + // rendering + var bookElement = document.getElementById("books"); + bookElement.innerHTML = TableTemplate(books); + + var bookCount = document.getElementById("bookCount"); + bookCount.innerHTML = `${books.length} books`; + + // add listeners for selecting book to view + Array.from(bookElement.querySelectorAll("tbody tr")) + .slice(1) // remove header from Array + .forEach((row) => { + row.addEventListener("click", (e) => { + // add listener to swap current book + document.getElementById("current").innerHTML = BookTemplate( + books[e.currentTarget.id] + ); + }); + }); + // add sorting callbacks + Array.from(bookElement.querySelectorAll("tbody tr th[data-sort-by]")).forEach( + (row) => { + row.addEventListener("click", function (e) { + renderTable(books, e.target.dataset.sortBy); // only add callback when there's a sortBy attribute + }); + } + ); + // mark currently active column + bookElement + .querySelector("tbody tr th[data-sort-by=" + sortState.sortBy + "]") + .classList.add(sortState.sortOrder); +} + +function apiResponseParsing(book) { + book.sortTitle = titleCleaner(book.title); + if (!book["isbn-10"] && book["isbn-13"]) { + book["isbn-10"] = ISBNfromEAN(book["isbn-13"]); + } + if (!book.coverURL && book["isbn-10"]) { + book.coverURL = + `https://images-na.ssl-images-amazon.com/images/P/` + + book["isbn-10"] + + `.01.LZZ.jpg`; + } + return book; +} + +function search(books, searchBy, includeChildrensBooks) { + searchBy = searchCleaner(searchBy); + books = books.filter( + ({ title, authors, genre, publisher, series, year, childrens }) => { + var inSearch = true; + if (searchBy !== "") { + inSearch = Object.values({ + title, + authors: authors.join(" "), + genre, + publisher, + series, + year, + }).find((field) => searchCleaner(field).indexOf(searchBy) !== -1); + } + if (!includeChildrensBooks) { + return inSearch && !childrens; + } + return inSearch; + } + ); + return books; +} + +function titleCleaner(title) { + return title + .replace('"', "") + .replace(":", "") + .replace(/^(An?|The)\s/i, ""); +} + +function searchCleaner(str) { + return str + .toLowerCase() + .replaceAll('"', "") + .replaceAll(":", "") + .replaceAll("'", "") + .replaceAll(" ", ""); +} + +function ISBNfromEAN(EAN) { + ISBN = EAN.slice(3, 12); + var checkdigit = + (11 - (ISBN.split("").reduce((s, n, k) => s + n * (10 - k), 0) % 11)) % 11; + return ISBN + (checkdigit === 10 ? "X" : checkdigit); +} + +function BookTemplate({ + "isbn-13": isbn13, + "isbn-10": isbn10, + authors, + coverURL, + description, + format, + notes, + publisher, + series, + signed, + title, + volume, + year, +}) { + return ` +
+

${title}

+

${authors}

+ ${[isbn10, isbn13].filter((v) => v != "").join(" / ")}
+ ${publisher}, ${year}
+ ${ + series + ? `${series}${volume ? `, Volume ${volume}` : ""}
` + : "" + } + ${signed ? "Signed by the author ✒
" : ""} + ${format} +
`; +} + +function TableRowTemplate({ + "isbn-13": isbn13, + "isbn-10": isbn10, + authors, + publisher, + rowNumber, + signed, + title, + year, +}) { + return ` + + ${title} ${ + signed ? '' : "" + } + + ${authors} + ${publisher} + ${year} + ${isbn13 ? isbn13 : isbn10} + `; +} + +function TableTemplate(books) { + return ` + + + + + + + ${books.reduce((acc, book) => { + return acc.concat(TableRowTemplate(book)); + }, "")}
TitleAuthorPublisherYearISBN
`; +} + +function AddBookTemplate() { + return `
add book form goes here
`; +} diff --git a/frontend/admin/favicon.ico b/frontend/admin/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..668a4f1b23da0edecebcfb394d75c27eeea27530 GIT binary patch literal 4414 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 Nvfu~*zwo~@@GnmE!0Z43 literal 0 HcmV?d00001 diff --git a/frontend/admin/favicon.png b/frontend/admin/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce2be3fcb4774ae7fbf54256a5bb87864a257987 GIT binary patch literal 70262 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 literal 0 HcmV?d00001 diff --git a/frontend/admin/index.html b/frontend/admin/index.html index d3e2931..3b08956 100644 --- a/frontend/admin/index.html +++ b/frontend/admin/index.html @@ -1,5 +1,47 @@ - - hello world - - \ No newline at end of file + + + Library + + + + + + + + +

+ +
No Book Selected
+
+
+ + diff --git a/frontend/admin/style.css b/frontend/admin/style.css new file mode 100644 index 0000000..43047cf --- /dev/null +++ b/frontend/admin/style.css @@ -0,0 +1,284 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ""; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* site CSS starts here */ + +body { + overflow: hidden; +} + +#header { + height: 30px; + width: calc(100vw - 20px); + padding: 4px 10px; + background-color: #f7f3dc; + border-bottom: 2px solid #d8d0a0; + font-family: "Libre Baskerville", sans-serif; +} + +#header h1 { + font-size: xx-large; + display: inline; +} + +#header .bookCount { + font-size: small; + color: #a29c77; +} + +#searchBox { + position: absolute; + right: 10px; + top: 7px; + text-align: right; + width: 800px; +} + +#searchBox input#search { + width: 300px; + font-size: 16px; + background: #f9f8ed; + padding: 2px 5px; + border: none; + border-bottom: 2px solid #d8d0a0; + font-family: "Libre Baskerville", sans-serif; +} + +#searchBox input:focus { + outline: none; +} + +#searchBox input::placeholder { + font-family: "Libre Baskerville", sans-serif; + color: #d8d0a0; +} + +#current { + background-color: #f7f3dc; + width: calc(40vw - 40px); + height: calc(100vh - 80px); + padding: 20px; + overflow: auto; + float: left; + position: relative; +} + +#books { + width: calc(60vw - 40px); + height: calc(100vh - 80px); + padding: 20px; + overflow: auto; + float: left; +} + +.bookTable th { + font-weight: bold; + text-align: left; + font-family: "Libre Baskerville", sans-serif; +} + +.bookTable th[data-sort-by] { + cursor: pointer; +} + +.bookTable th[data-sort-by]::after { + content: "\2195"; + position: relative; + left: 4px; +} + +.bookTable th.asc::after { + content: "\2191"; + font-size: small; + position: relative; + left: 4px; + bottom: 1px; +} + +.bookTable th.desc::after { + content: "\2193"; + font-size: small; + position: relative; + left: 4px; + bottom: 1px; +} + +.bookTable td, +.bookTable th { + padding: 5px; + min-width: 50px; +} + +.tRow:nth-child(odd) { + background: #f9f8ed; + border-bottom: 1px solid #d8d0a0; +} + +.bookTable .tRow { + cursor: pointer; +} + +.bookTable .tRow .title { + font-style: italic; + max-width: 600px; +} + +#current h1 { + font-size: x-large; + font-weight: bold; + font-style: italic; + padding: 0 0 5px 0; +} + +#current h2 { + font-size: large; + padding: 7px 0; +} + +#current img { + opacity: 0.5; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: auto; +} + +#current .bookDetails { + position: relative; + background-color: rgba(255, 255, 255, 0.8); + padding: 10px; + margin: 0; + width: 75%; + border-radius: 5px; +} + +#current .description p { + padding: 20px 0; +} diff --git a/frontend/frontend.go b/frontend/frontend.go index 5693779..03e9789 100644 --- a/frontend/frontend.go +++ b/frontend/frontend.go @@ -8,10 +8,13 @@ import ( //go:embed files var static embed.FS +//go:embed admin +var admin embed.FS + func Root() (fs.FS, error) { return fs.Sub(static, "files") } func AdminRoot() (fs.FS, error) { - return fs.Sub(static, "admin") + return fs.Sub(admin, "admin") } -- 2.45.2 From 382d3bab61454dcb78e0cd527b32127acd85d69d Mon Sep 17 00:00:00 2001 From: David Ashby Date: Mon, 1 Jan 2024 20:38:42 -0500 Subject: [PATCH 06/10] unify frontends --- cmd/serve/api.go | 66 +++++---- cmd/serve/main.go | 25 ++-- frontend/admin/app.js | 217 ---------------------------- frontend/admin/favicon.ico | Bin 4414 -> 0 bytes frontend/admin/favicon.png | Bin 70262 -> 0 bytes frontend/admin/index.html | 47 ------ frontend/admin/style.css | 284 ------------------------------------- frontend/frontend.go | 7 - 8 files changed, 48 insertions(+), 598 deletions(-) delete mode 100644 frontend/admin/app.js delete mode 100644 frontend/admin/favicon.ico delete mode 100644 frontend/admin/favicon.png delete mode 100644 frontend/admin/index.html delete mode 100644 frontend/admin/style.css diff --git a/cmd/serve/api.go b/cmd/serve/api.go index cdf3c8f..d76977c 100644 --- a/cmd/serve/api.go +++ b/cmd/serve/api.go @@ -11,21 +11,17 @@ import ( ) type Router struct { - static fs.FS - lib Library - rcol RecordCollection + static fs.FS + lib Library + rcol RecordCollection + ts *tailscale.LocalClient + isAdmin bool } -type AdminRouter struct { - static fs.FS - lib Library - ts *tailscale.LocalClient -} +type path map[string]func() -type handler map[string]func() - -func (h handler) Handle(w http.ResponseWriter, req *http.Request) { - if f, ok := h[req.Method]; ok { +func (h path) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if f, ok := h[r.Method]; ok { f() return } @@ -50,31 +46,33 @@ func writeJSON(w http.ResponseWriter, b any, status int) { func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { - case "/api/records": - handler{ - http.MethodGet: func() { getRecords(router.rcol, w, r) }, - }.Handle(w, r) - case "/api/books": - handler{ - http.MethodGet: func() { getBooks(router.lib, w, r) }, - }.Handle(w, r) - default: - static(router.static).ServeHTTP(w, r) - } -} - -func (router *AdminRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { + case "/api/mode": + path{ + http.MethodGet: func() { + writeJSON(w, struct{ Admin bool }{Admin: router.isAdmin}, http.StatusOK) + }, + }.ServeHTTP(w, r) case "/api/whoami": - handler{ + if !router.isAdmin { + http.NotFoundHandler().ServeHTTP(w, r) + return + } + path{ http.MethodGet: func() { getWhoAmI(router.ts, w, r) }, - }.Handle(w, r) + }.ServeHTTP(w, r) + case "/api/records": + path{ + http.MethodGet: func() { getRecords(router.rcol, w, r) }, + }.ServeHTTP(w, r) case "/api/books": - handler{ - http.MethodGet: func() { getBooks(router.lib, w, r) }, - http.MethodPost: func() { addBook(router.lib, w, r) }, - http.MethodDelete: func() { deleteBook(router.lib, w, r) }, - }.Handle(w, r) + p := path{ + http.MethodGet: func() { getBooks(router.lib, w, r) }, + } + if router.isAdmin { + p[http.MethodPost] = func() { addBook(router.lib, w, r) } + p[http.MethodDelete] = func() { deleteBook(router.lib, w, r) } + } + p.ServeHTTP(w, r) default: static(router.static).ServeHTTP(w, r) } diff --git a/cmd/serve/main.go b/cmd/serve/main.go index 267a23d..2c2e6dc 100644 --- a/cmd/serve/main.go +++ b/cmd/serve/main.go @@ -55,24 +55,26 @@ func main() { c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile, )) - frontendRoot := must.Get(frontend.Root()) - adminRoot := must.Get(frontend.AdminRoot()) + staticRoot := must.Get(frontend.Root()) servers := make(chan (*http.Server), 3) errGroup := errgroup.Group{} errGroup.Go(func() error { return start(servers)( publicServer(8080, &Router{ - static: frontendRoot, - lib: lib, - rcol: discogsCache, + static: staticRoot, + lib: lib, + rcol: discogsCache, + isAdmin: false, })) }) errGroup.Go(func() error { return start(servers)( - tailscaleListener("library-admin", &AdminRouter{ - static: adminRoot, - lib: lib, + tailscaleListener("library-admin", &Router{ + static: staticRoot, + lib: lib, + rcol: discogsCache, + isAdmin: true, })) }) errGroup.Go(func() error { @@ -143,10 +145,15 @@ func publicServer(port int, handler http.Handler) (*http.Server, net.Listener, e return server, ln, nil } -func tailscaleListener(hostname string, handler *AdminRouter) (*http.Server, net.Listener, error) { +func tailscaleListener(hostname string, handler *Router) (*http.Server, net.Listener, error) { s := &tsnet.Server{ Dir: ".config/" + hostname, Hostname: hostname, + Logf: func(s string, a ...any) { // silence most tsnet logs + if strings.HasPrefix(s, "To start this tsnet server") { + log.Printf(s, a...) + } + }, } ln, err := s.Listen("tcp", ":80") if err != nil { diff --git a/frontend/admin/app.js b/frontend/admin/app.js deleted file mode 100644 index d8c2d4c..0000000 --- a/frontend/admin/app.js +++ /dev/null @@ -1,217 +0,0 @@ -var sortState = { - sortBy: "sortAuthor", - sortOrder: "asc", -}; - -function init() { - fetch("/api/books") - .then((response) => response.json()) - .then((books) => { - // prepare response - books.forEach(apiResponseParsing); - document.getElementById("search").addEventListener("input", (e) => { - renderTable( - search( - books, - e.target.value, - document.getElementById("childrens").checked - ) - ); - }); - document.getElementById("childrens").addEventListener("change", (e) => { - renderTable( - search( - books, - document.getElementById("search").value, - e.target.checked - ) - ); - }); - renderTable( - search(books, "", document.getElementById("childrens").checked) - ); - }); -} - -function renderTable(books, sortField) { - if (sortField) { - if (sortState.sortBy === sortField && sortState.sortOrder === "asc") { - sortState.sortOrder = "desc"; - } else { - sortState.sortOrder = "asc"; - } - sortState.sortBy = sortField; - } - books.sort((one, two) => - (one[sortState.sortBy] + one["sortTitle"]).localeCompare( - two[sortState.sortBy] + two["sortTitle"] - ) - ); - if (sortState.sortOrder === "desc") { - books.reverse(); - } - books.forEach((e, i) => (e.rowNumber = i)); // re-key - - // rendering - var bookElement = document.getElementById("books"); - bookElement.innerHTML = TableTemplate(books); - - var bookCount = document.getElementById("bookCount"); - bookCount.innerHTML = `${books.length} books`; - - // add listeners for selecting book to view - Array.from(bookElement.querySelectorAll("tbody tr")) - .slice(1) // remove header from Array - .forEach((row) => { - row.addEventListener("click", (e) => { - // add listener to swap current book - document.getElementById("current").innerHTML = BookTemplate( - books[e.currentTarget.id] - ); - }); - }); - // add sorting callbacks - Array.from(bookElement.querySelectorAll("tbody tr th[data-sort-by]")).forEach( - (row) => { - row.addEventListener("click", function (e) { - renderTable(books, e.target.dataset.sortBy); // only add callback when there's a sortBy attribute - }); - } - ); - // mark currently active column - bookElement - .querySelector("tbody tr th[data-sort-by=" + sortState.sortBy + "]") - .classList.add(sortState.sortOrder); -} - -function apiResponseParsing(book) { - book.sortTitle = titleCleaner(book.title); - if (!book["isbn-10"] && book["isbn-13"]) { - book["isbn-10"] = ISBNfromEAN(book["isbn-13"]); - } - if (!book.coverURL && book["isbn-10"]) { - book.coverURL = - `https://images-na.ssl-images-amazon.com/images/P/` + - book["isbn-10"] + - `.01.LZZ.jpg`; - } - return book; -} - -function search(books, searchBy, includeChildrensBooks) { - searchBy = searchCleaner(searchBy); - books = books.filter( - ({ title, authors, genre, publisher, series, year, childrens }) => { - var inSearch = true; - if (searchBy !== "") { - inSearch = Object.values({ - title, - authors: authors.join(" "), - genre, - publisher, - series, - year, - }).find((field) => searchCleaner(field).indexOf(searchBy) !== -1); - } - if (!includeChildrensBooks) { - return inSearch && !childrens; - } - return inSearch; - } - ); - return books; -} - -function titleCleaner(title) { - return title - .replace('"', "") - .replace(":", "") - .replace(/^(An?|The)\s/i, ""); -} - -function searchCleaner(str) { - return str - .toLowerCase() - .replaceAll('"', "") - .replaceAll(":", "") - .replaceAll("'", "") - .replaceAll(" ", ""); -} - -function ISBNfromEAN(EAN) { - ISBN = EAN.slice(3, 12); - var checkdigit = - (11 - (ISBN.split("").reduce((s, n, k) => s + n * (10 - k), 0) % 11)) % 11; - return ISBN + (checkdigit === 10 ? "X" : checkdigit); -} - -function BookTemplate({ - "isbn-13": isbn13, - "isbn-10": isbn10, - authors, - coverURL, - description, - format, - notes, - publisher, - series, - signed, - title, - volume, - year, -}) { - return ` -
-

${title}

-

${authors}

- ${[isbn10, isbn13].filter((v) => v != "").join(" / ")}
- ${publisher}, ${year}
- ${ - series - ? `${series}${volume ? `, Volume ${volume}` : ""}
` - : "" - } - ${signed ? "Signed by the author ✒
" : ""} - ${format} -
`; -} - -function TableRowTemplate({ - "isbn-13": isbn13, - "isbn-10": isbn10, - authors, - publisher, - rowNumber, - signed, - title, - year, -}) { - return ` - - ${title} ${ - signed ? '' : "" - } - - ${authors} - ${publisher} - ${year} - ${isbn13 ? isbn13 : isbn10} - `; -} - -function TableTemplate(books) { - return ` - - - - - - - ${books.reduce((acc, book) => { - return acc.concat(TableRowTemplate(book)); - }, "")}
TitleAuthorPublisherYearISBN
`; -} - -function AddBookTemplate() { - return `
add book form goes here
`; -} diff --git a/frontend/admin/favicon.ico b/frontend/admin/favicon.ico deleted file mode 100644 index 668a4f1b23da0edecebcfb394d75c27eeea27530..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4414 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 Nvfu~*zwo~@@GnmE!0Z43 diff --git a/frontend/admin/favicon.png b/frontend/admin/favicon.png deleted file mode 100644 index ce2be3fcb4774ae7fbf54256a5bb87864a257987..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70262 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 diff --git a/frontend/admin/index.html b/frontend/admin/index.html deleted file mode 100644 index 3b08956..0000000 --- a/frontend/admin/index.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - Library - - - - - - - - -

- -
No Book Selected
-
-
- - diff --git a/frontend/admin/style.css b/frontend/admin/style.css deleted file mode 100644 index 43047cf..0000000 --- a/frontend/admin/style.css +++ /dev/null @@ -1,284 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, -body, -div, -span, -applet, -object, -iframe, -h1, -h2, -h3, -h4, -h5, -h6, -p, -blockquote, -pre, -a, -abbr, -acronym, -address, -big, -cite, -code, -del, -dfn, -em, -img, -ins, -kbd, -q, -s, -samp, -small, -strike, -strong, -sub, -sup, -tt, -var, -b, -u, -i, -center, -dl, -dt, -dd, -ol, -ul, -li, -fieldset, -form, -label, -legend, -table, -caption, -tbody, -tfoot, -thead, -tr, -th, -td, -article, -aside, -canvas, -details, -embed, -figure, -figcaption, -footer, -header, -hgroup, -menu, -nav, -output, -ruby, -section, -summary, -time, -mark, -audio, -video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -menu, -nav, -section { - display: block; -} -body { - line-height: 1; -} -ol, -ul { - list-style: none; -} -blockquote, -q { - quotes: none; -} -blockquote:before, -blockquote:after, -q:before, -q:after { - content: ""; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} - -/* site CSS starts here */ - -body { - overflow: hidden; -} - -#header { - height: 30px; - width: calc(100vw - 20px); - padding: 4px 10px; - background-color: #f7f3dc; - border-bottom: 2px solid #d8d0a0; - font-family: "Libre Baskerville", sans-serif; -} - -#header h1 { - font-size: xx-large; - display: inline; -} - -#header .bookCount { - font-size: small; - color: #a29c77; -} - -#searchBox { - position: absolute; - right: 10px; - top: 7px; - text-align: right; - width: 800px; -} - -#searchBox input#search { - width: 300px; - font-size: 16px; - background: #f9f8ed; - padding: 2px 5px; - border: none; - border-bottom: 2px solid #d8d0a0; - font-family: "Libre Baskerville", sans-serif; -} - -#searchBox input:focus { - outline: none; -} - -#searchBox input::placeholder { - font-family: "Libre Baskerville", sans-serif; - color: #d8d0a0; -} - -#current { - background-color: #f7f3dc; - width: calc(40vw - 40px); - height: calc(100vh - 80px); - padding: 20px; - overflow: auto; - float: left; - position: relative; -} - -#books { - width: calc(60vw - 40px); - height: calc(100vh - 80px); - padding: 20px; - overflow: auto; - float: left; -} - -.bookTable th { - font-weight: bold; - text-align: left; - font-family: "Libre Baskerville", sans-serif; -} - -.bookTable th[data-sort-by] { - cursor: pointer; -} - -.bookTable th[data-sort-by]::after { - content: "\2195"; - position: relative; - left: 4px; -} - -.bookTable th.asc::after { - content: "\2191"; - font-size: small; - position: relative; - left: 4px; - bottom: 1px; -} - -.bookTable th.desc::after { - content: "\2193"; - font-size: small; - position: relative; - left: 4px; - bottom: 1px; -} - -.bookTable td, -.bookTable th { - padding: 5px; - min-width: 50px; -} - -.tRow:nth-child(odd) { - background: #f9f8ed; - border-bottom: 1px solid #d8d0a0; -} - -.bookTable .tRow { - cursor: pointer; -} - -.bookTable .tRow .title { - font-style: italic; - max-width: 600px; -} - -#current h1 { - font-size: x-large; - font-weight: bold; - font-style: italic; - padding: 0 0 5px 0; -} - -#current h2 { - font-size: large; - padding: 7px 0; -} - -#current img { - opacity: 0.5; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: auto; -} - -#current .bookDetails { - position: relative; - background-color: rgba(255, 255, 255, 0.8); - padding: 10px; - margin: 0; - width: 75%; - border-radius: 5px; -} - -#current .description p { - padding: 20px 0; -} diff --git a/frontend/frontend.go b/frontend/frontend.go index 03e9789..49c6b61 100644 --- a/frontend/frontend.go +++ b/frontend/frontend.go @@ -8,13 +8,6 @@ import ( //go:embed files var static embed.FS -//go:embed admin -var admin embed.FS - func Root() (fs.FS, error) { return fs.Sub(static, "files") } - -func AdminRoot() (fs.FS, error) { - return fs.Sub(admin, "admin") -} -- 2.45.2 From 86f643ad0b5776effcebd934f0eb8e800409526f Mon Sep 17 00:00:00 2001 From: David Ashby Date: Tue, 2 Jan 2024 20:52:21 -0500 Subject: [PATCH 07/10] more backend --- cmd/serve/api.go | 23 +++++ cmd/serve/main.go | 13 ++- frontend/files/app.js | 49 ++++++++++ frontend/files/index.html | 1 + frontend/files/records/style.css | 4 +- frontend/files/style.css | 4 + go.mod | 1 + go.sum | 2 + query/amazon.go | 13 +++ query/funcs.go | 28 ++++++ query/googlebooks.go | 158 +++++++++++++++++++++++++++++++ query/null.go | 11 +++ query/openlibrary.go | 46 +++++++++ 13 files changed, 349 insertions(+), 4 deletions(-) create mode 100644 query/amazon.go create mode 100644 query/funcs.go create mode 100644 query/googlebooks.go create mode 100644 query/null.go create mode 100644 query/openlibrary.go diff --git a/cmd/serve/api.go b/cmd/serve/api.go index d76977c..6d03a4a 100644 --- a/cmd/serve/api.go +++ b/cmd/serve/api.go @@ -14,6 +14,7 @@ type Router struct { static fs.FS lib Library rcol RecordCollection + query Query ts *tailscale.LocalClient isAdmin bool } @@ -73,6 +74,14 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { p[http.MethodDelete] = func() { deleteBook(router.lib, w, r) } } p.ServeHTTP(w, r) + case "/api/query": + if !router.isAdmin { + http.NotFoundHandler().ServeHTTP(w, r) + return + } + path{ + http.MethodPost: func() { lookupBook(router.query, w, r) }, + }.ServeHTTP(w, r) default: static(router.static).ServeHTTP(w, r) } @@ -159,6 +168,20 @@ func getWhoAmI(ts *tailscale.LocalClient, w http.ResponseWriter, r *http.Request writeJSON(w, whois.UserProfile, http.StatusOK) } +func lookupBook(query Query, w http.ResponseWriter, r *http.Request) { + isbn := r.FormValue("isbn") + if len(isbn) != 10 && len(isbn) != 13 { + writeJSONerror(w, "invalid isbn", http.StatusBadRequest) + return + } + book, err := query.GetByISBN(isbn) + if err != nil { + writeJSONerror(w, err.Error(), http.StatusInternalServerError) + return + } + writeJSON(w, book, http.StatusOK) +} + func static(f fs.FS) http.Handler { return http.FileServer(http.FS(f)) } diff --git a/cmd/serve/main.go b/cmd/serve/main.go index 2c2e6dc..dd20ff1 100644 --- a/cmd/serve/main.go +++ b/cmd/serve/main.go @@ -15,6 +15,7 @@ import ( "git.yetaga.in/alazyreader/library/database" "git.yetaga.in/alazyreader/library/frontend" "git.yetaga.in/alazyreader/library/media" + "git.yetaga.in/alazyreader/library/query" "github.com/kelseyhightower/envconfig" "golang.org/x/sync/errgroup" "tailscale.com/tsnet" @@ -35,6 +36,10 @@ type RecordCollection interface { GetAllRecords(context.Context) ([]media.Record, error) } +type Query interface { + GetByISBN(string) (*media.Book, error) +} + func main() { var c config.Config must.Do(envconfig.Process("library", &c)) @@ -55,6 +60,8 @@ func main() { c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile, )) + queryProvider := &query.GoogleBooks{} + staticRoot := must.Get(frontend.Root()) servers := make(chan (*http.Server), 3) @@ -74,6 +81,7 @@ func main() { static: staticRoot, lib: lib, rcol: discogsCache, + query: queryProvider, isAdmin: true, })) }) @@ -137,11 +145,11 @@ func shutdown(servers chan (*http.Server)) error { func publicServer(port int, handler http.Handler) (*http.Server, net.Listener, error) { server := &http.Server{Handler: handler} - ln, err := net.Listen("tcp", fmt.Sprintf(":%d", 8080)) + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { return nil, nil, err } - log.Println("starting public server") + log.Printf("public server: http://0.0.0.0:%d/", port) return server, ln, nil } @@ -163,6 +171,7 @@ func tailscaleListener(hostname string, handler *Router) (*http.Server, net.List if err != nil { return nil, nil, err } + log.Printf("management server: http://%s/", hostname) server := &http.Server{Handler: handler} return server, ln, nil } diff --git a/frontend/files/app.js b/frontend/files/app.js index 78e598a..8e2a59b 100644 --- a/frontend/files/app.js +++ b/frontend/files/app.js @@ -3,7 +3,23 @@ var sortState = { sortOrder: "asc", }; +var admin = false; + function init() { + fetch("/api/mode") + .then((response) => response.json()) + .then((resp) => (admin = resp.Admin)) + .then(() => { + if (admin) { + var element = document.getElementById("addBook"); + element.addEventListener("click", (e) => { + e.preventDefault(); + renderAddBookView(); + }); + element.classList.remove("hidden"); + } + }); + fetch("/api/books") .then((response) => response.json()) .then((books) => { @@ -33,6 +49,10 @@ function init() { }); } +function renderAddBookView() { + document.getElementById("current").innerHTML = AddBookTemplate(); +} + function renderTable(books, sortField) { if (sortField) { if (sortState.sortBy === sortField && sortState.sortOrder === "asc") { @@ -173,6 +193,7 @@ function BookTemplate({ } ${signed ? "Signed by the author ✒
" : ""} ${format} + ${admin ? `Edit Book` : ""} `; } @@ -211,3 +232,31 @@ function TableTemplate(books) { return acc.concat(TableRowTemplate(book)); }, "")} `; } + +function AddBookTemplate() { + return `
+
${[ + { name: "ISBN10", type: "text" }, + { name: "ISBN13", type: "text" }, + { name: "Title", type: "text" }, + { name: "Authors", type: "text" }, + { name: "SortAuthor", type: "text" }, + { name: "Format", type: "text" }, + { name: "Genre", type: "text" }, + { name: "Publisher", type: "text" }, + { name: "Series", type: "text" }, + { name: "Volume", type: "text" }, + { name: "Year", type: "text" }, + { name: "Signed", type: "checkbox" }, + // { name: "Description", type: "text" }, + // { name: "Notes", type: "text" }, + { name: "CoverURL", type: "text" }, + { name: "Childrens", type: "checkbox" }, + ].reduce((acc, field) => { + return acc.concat( + `
` + ); + }, "")} +
+
`; +} diff --git a/frontend/files/index.html b/frontend/files/index.html index 5997b38..d92ea4e 100644 --- a/frontend/files/index.html +++ b/frontend/files/index.html @@ -31,6 +31,7 @@ href="https://git.yetaga.in/alazyreader/library" >git +