management server listener #17
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
/server
|
||||
/manager
|
||||
/cli
|
||||
*.properties
|
||||
.DS_Store
|
||||
/vendor
|
||||
.recordsCache
|
||||
.config
|
16
Makefile
16
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
|
||||
|
179
cmd/serve/api.go
Normal file
179
cmd/serve/api.go
Normal file
@ -0,0 +1,179 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.yetaga.in/alazyreader/library/media"
|
||||
"tailscale.com/client/tailscale"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
static fs.FS
|
||||
lib Library
|
||||
rcol RecordCollection
|
||||
query Query
|
||||
ts *tailscale.LocalClient
|
||||
isAdmin bool
|
||||
}
|
||||
|
||||
type path map[string]func()
|
||||
|
||||
func (h path) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if f, ok := h[r.Method]; ok {
|
||||
f()
|
||||
return
|
||||
}
|
||||
writeJSONerror(w, "method not supported", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func writeJSONerror(w http.ResponseWriter, err string, status int) {
|
||||
log.Println(err)
|
||||
writeJSON(w, struct{ Status, Reason string }{Status: "error", Reason: err}, status)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, b any, status int) {
|
||||
bytes, err := json.Marshal(b)
|
||||
if err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
w.Write(bytes)
|
||||
w.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
func (router *Router) 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":
|
||||
if !router.isAdmin {
|
||||
http.NotFoundHandler().ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
path{
|
||||
http.MethodGet: func() { getWhoAmI(router.ts, w, r) },
|
||||
}.ServeHTTP(w, r)
|
||||
case "/api/records":
|
||||
path{
|
||||
http.MethodGet: func() { getRecords(router.rcol, w, r) },
|
||||
}.ServeHTTP(w, r)
|
||||
case "/api/books":
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func getBooks(l Library, w http.ResponseWriter, r *http.Request) {
|
||||
books, err := l.GetAllBooks(r.Context())
|
||||
if err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, books, http.StatusOK)
|
||||
}
|
||||
|
||||
func addBook(l Library, w http.ResponseWriter, r *http.Request) {
|
||||
book, err := ReadBody[media.Book](r.Body)
|
||||
if err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err = l.AddBook(r.Context(), book); err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func deleteBook(l Library, w http.ResponseWriter, r *http.Request) {
|
||||
book, err := ReadBody[media.Book](r.Body)
|
||||
if err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err = l.DeleteBook(r.Context(), book); err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func getRecords(l RecordCollection, w http.ResponseWriter, r *http.Request) {
|
||||
records, err := l.GetAllRecords(r.Context())
|
||||
if err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, records, http.StatusOK)
|
||||
}
|
||||
|
||||
func getWhoAmI(ts *tailscale.LocalClient, w http.ResponseWriter, r *http.Request) {
|
||||
whois, err := ts.WhoIs(r.Context(), r.RemoteAddr)
|
||||
if err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, whois.UserProfile, http.StatusOK)
|
||||
}
|
||||
|
||||
func lookupBook(query Query, w http.ResponseWriter, r *http.Request) {
|
||||
req, err := ReadBody[media.Book](r.Body)
|
||||
if err != nil {
|
||||
writeJSONerror(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
book, err := query.GetByISBN(req.ISBN13)
|
||||
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))
|
||||
}
|
||||
|
||||
func ReadBody[T any](r io.ReadCloser) (*T, error) {
|
||||
t := new(T)
|
||||
if r == nil {
|
||||
return t, fmt.Errorf("no body provided")
|
||||
}
|
||||
defer r.Close()
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return t, fmt.Errorf("error reading body: %w", err)
|
||||
}
|
||||
err = json.Unmarshal(b, t)
|
||||
if err != nil {
|
||||
return t, fmt.Errorf("error reading body: %w", err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
@ -2,10 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/fs"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -13,100 +15,78 @@ 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"
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
type Library interface {
|
||||
GetAllBooks(context.Context) ([]media.Book, error)
|
||||
AddBook(context.Context, *media.Book) error
|
||||
DeleteBook(context.Context, *media.Book) error
|
||||
}
|
||||
|
||||
type RecordCollection interface {
|
||||
GetAllRecords(context.Context) ([]media.Record, error)
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
static fs.FS
|
||||
lib Library
|
||||
rcol RecordCollection
|
||||
}
|
||||
|
||||
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 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))
|
||||
type Query interface {
|
||||
GetByISBN(string) (*media.Book, error)
|
||||
}
|
||||
|
||||
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
|
||||
if c.DBType == "memory" {
|
||||
lib = &database.Memory{}
|
||||
} else if c.DBType == "sql" {
|
||||
sqllib, latest, run, err := setupSQL(c)
|
||||
if err != nil {
|
||||
log.Fatalf("sql connection err: %v", err)
|
||||
}
|
||||
log.Printf("latest migration: %d; migrations run: %d", latest, run)
|
||||
lib = sqllib
|
||||
}
|
||||
discogsCache := must.Get(database.NewDiscogsCache(
|
||||
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)
|
||||
errGroup := errgroup.Group{}
|
||||
errGroup.Go(func() error {
|
||||
return start(servers)(publicServer(8080, &Router{
|
||||
static: staticRoot,
|
||||
lib: lib,
|
||||
rcol: discogsCache,
|
||||
isAdmin: false,
|
||||
}))
|
||||
})
|
||||
errGroup.Go(func() error {
|
||||
return start(servers)(tailscaleListener("library-admin", &Router{
|
||||
static: staticRoot,
|
||||
lib: lib,
|
||||
rcol: discogsCache,
|
||||
query: queryProvider,
|
||||
isAdmin: true,
|
||||
}))
|
||||
})
|
||||
errGroup.Go(func() error {
|
||||
return shutdown(servers)
|
||||
})
|
||||
log.Println(errGroup.Wait())
|
||||
}
|
||||
|
||||
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)
|
||||
@ -114,30 +94,77 @@ func main() {
|
||||
if c.DiscogsToken != "" {
|
||||
c.DiscogsToken = obscureStr(c.DiscogsToken, 3)
|
||||
}
|
||||
log.Fatalf("vars: %+v", c)
|
||||
return nil, 0, 0, fmt.Errorf("invalid config; vars provided: %+v", c)
|
||||
}
|
||||
lib, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName)
|
||||
sql, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
err = lib.PrepareDatabase(context.Background())
|
||||
err = sql.PrepareDatabase(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
latest, run, err := lib.RunMigrations(context.Background())
|
||||
latest, run, err := sql.RunMigrations(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
return nil, 0, 0, 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))
|
||||
return sql, latest, run, nil
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
servers <- s
|
||||
return s.Serve(l)
|
||||
}
|
||||
}
|
||||
|
||||
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 shutdownerr := server.Shutdown(ctx); shutdownerr != nil {
|
||||
err = shutdownerr
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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", port))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Printf("public server: http://0.0.0.0:%d/", port)
|
||||
return server, ln, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
handler.ts, err = s.LocalClient()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Printf("management server: http://%s/", hostname)
|
||||
return &http.Server{Handler: handler}, ln, nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
DBType string `default:"sql"`
|
||||
DBUser string
|
||||
DBPass string
|
||||
DBHost string
|
||||
|
@ -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])
|
||||
|
@ -3,61 +3,130 @@ var sortState = {
|
||||
sortOrder: "asc",
|
||||
};
|
||||
|
||||
function init() {
|
||||
fetch("/api/books")
|
||||
var admin = false;
|
||||
|
||||
var books;
|
||||
|
||||
function checkAdminMode() {
|
||||
fetch("/api/mode")
|
||||
.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)
|
||||
);
|
||||
.then((resp) => (admin = resp.Admin))
|
||||
.then(() => {
|
||||
if (admin) {
|
||||
var element = document.getElementById("addBook");
|
||||
element.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
renderAddBookView();
|
||||
});
|
||||
element.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(books, sortField) {
|
||||
if (sortField) {
|
||||
if (sortState.sortBy === sortField && sortState.sortOrder === "asc") {
|
||||
sortState.sortOrder = "desc";
|
||||
function loadBookList() {
|
||||
fetch("/api/books")
|
||||
.then((response) => response.json())
|
||||
.then((list) => {
|
||||
// prepare response
|
||||
list.forEach(apiResponseParsing);
|
||||
books = list;
|
||||
document.getElementById("search").addEventListener("input", rerender);
|
||||
document.getElementById("childrens").addEventListener("change", rerender);
|
||||
rerender();
|
||||
});
|
||||
}
|
||||
|
||||
function rerender() {
|
||||
var searchValue = document.getElementById("search").value;
|
||||
var childrens = document.getElementById("childrens").checked;
|
||||
renderTable(search(searchValue, childrens));
|
||||
}
|
||||
|
||||
function init() {
|
||||
checkAdminMode();
|
||||
loadBookList();
|
||||
}
|
||||
|
||||
function renderAddBookView() {
|
||||
document.getElementById("current").innerHTML = AddBookTemplate();
|
||||
document.getElementById("lookup").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (document.getElementById("isbn-13").value.length === 13) {
|
||||
getPossibleBooks(document.getElementById("isbn-13").value);
|
||||
} else {
|
||||
sortState.sortOrder = "asc";
|
||||
console.log("no isbn");
|
||||
}
|
||||
});
|
||||
document.getElementById("save").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
saveBook({
|
||||
title: document.getElementById("title").value,
|
||||
authors: document.getElementById("authors").value.split(";"),
|
||||
sortAuthor: document.getElementById("sortAuthor").value,
|
||||
"isbn-10": document.getElementById("isbn-10").value,
|
||||
"isbn-13": document.getElementById("isbn-13").value,
|
||||
publisher: document.getElementById("publisher").value,
|
||||
format: document.getElementById("format").value,
|
||||
genre: document.getElementById("genre").value,
|
||||
series: document.getElementById("series").value,
|
||||
volume: document.getElementById("volume").value,
|
||||
year: document.getElementById("year").value,
|
||||
coverURL: document.getElementById("coverURL").value,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getPossibleBooks(isbn) {
|
||||
fetch("/api/query", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ "isbn-13": isbn }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
Object.keys(json).forEach((key) => {
|
||||
var elem = document.getElementById(key);
|
||||
if (elem !== null) {
|
||||
elem.value = json[key];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function saveBook(book) {
|
||||
fetch("/api/books", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(book),
|
||||
}).then(() => {
|
||||
clearAddBookForm();
|
||||
loadBookList();
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(bookList, sortField) {
|
||||
if (sortField) {
|
||||
sortState.sortOrder =
|
||||
sortState.sortBy === sortField && sortState.sortOrder === "asc"
|
||||
? "desc"
|
||||
: "asc";
|
||||
sortState.sortBy = sortField;
|
||||
}
|
||||
books.sort((one, two) =>
|
||||
bookList.sort((one, two) =>
|
||||
(one[sortState.sortBy] + one["sortTitle"]).localeCompare(
|
||||
two[sortState.sortBy] + two["sortTitle"]
|
||||
)
|
||||
);
|
||||
if (sortState.sortOrder === "desc") {
|
||||
books.reverse();
|
||||
bookList.reverse();
|
||||
}
|
||||
books.forEach((e, i) => (e.rowNumber = i)); // re-key
|
||||
bookList.forEach((e, i) => (e.rowNumber = i)); // re-key
|
||||
|
||||
// rendering
|
||||
var bookElement = document.getElementById("books");
|
||||
bookElement.innerHTML = TableTemplate(books);
|
||||
bookElement.innerHTML = TableTemplate(bookList);
|
||||
|
||||
var bookCount = document.getElementById("bookCount");
|
||||
bookCount.innerHTML = `${books.length} books`;
|
||||
document.getElementById("bookCount").innerHTML = `${bookList.length} books`;
|
||||
|
||||
// add listeners for selecting book to view
|
||||
Array.from(bookElement.querySelectorAll("tbody tr"))
|
||||
@ -66,7 +135,7 @@ function renderTable(books, sortField) {
|
||||
row.addEventListener("click", (e) => {
|
||||
// add listener to swap current book
|
||||
document.getElementById("current").innerHTML = BookTemplate(
|
||||
books[e.currentTarget.id]
|
||||
bookList[e.currentTarget.id]
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -74,7 +143,8 @@ function renderTable(books, sortField) {
|
||||
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
|
||||
// only add callback when there's a sortBy attribute
|
||||
renderTable(bookList, e.target.dataset.sortBy);
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -98,9 +168,9 @@ function apiResponseParsing(book) {
|
||||
return book;
|
||||
}
|
||||
|
||||
function search(books, searchBy, includeChildrensBooks) {
|
||||
function search(searchBy, includeChildrensBooks) {
|
||||
searchBy = searchCleaner(searchBy);
|
||||
books = books.filter(
|
||||
return books.filter(
|
||||
({ title, authors, genre, publisher, series, year, childrens }) => {
|
||||
var inSearch = true;
|
||||
if (searchBy !== "") {
|
||||
@ -119,7 +189,6 @@ function search(books, searchBy, includeChildrensBooks) {
|
||||
return inSearch;
|
||||
}
|
||||
);
|
||||
return books;
|
||||
}
|
||||
|
||||
function titleCleaner(title) {
|
||||
@ -145,14 +214,20 @@ function ISBNfromEAN(EAN) {
|
||||
return ISBN + (checkdigit === 10 ? "X" : checkdigit);
|
||||
}
|
||||
|
||||
function clearAddBookForm() {
|
||||
document
|
||||
.getElementById("newBookForm")
|
||||
.childNodes.forEach((node) =>
|
||||
node.nodeName === "LABEL" ? (node.lastChild.value = "") : null
|
||||
);
|
||||
}
|
||||
|
||||
function BookTemplate({
|
||||
"isbn-13": isbn13,
|
||||
"isbn-10": isbn10,
|
||||
authors,
|
||||
coverURL,
|
||||
description,
|
||||
format,
|
||||
notes,
|
||||
publisher,
|
||||
series,
|
||||
signed,
|
||||
@ -173,6 +248,7 @@ function BookTemplate({
|
||||
}
|
||||
${signed ? "<span>Signed by the author ✒</span><br/>" : ""}
|
||||
<span>${format}</span>
|
||||
${admin ? `<a href="#">Edit Book</a>` : ""}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@ -211,3 +287,36 @@ function TableTemplate(books) {
|
||||
return acc.concat(TableRowTemplate(book));
|
||||
}, "")} </table>`;
|
||||
}
|
||||
|
||||
function AddBookTemplate() {
|
||||
return `<div class="addBookView">
|
||||
<div id="newBookForm">
|
||||
${[
|
||||
{ name: "Title", id: "title", type: "text" },
|
||||
{ name: "Authors", id: "authors", type: "text" },
|
||||
{ name: "SortAuthor", id: "sortAuthor", type: "text" },
|
||||
{ name: "ISBN10", id: "isbn-10", type: "text" },
|
||||
{ name: "ISBN13", id: "isbn-13", type: "text" },
|
||||
{ name: "Publisher", id: "publisher", type: "text" },
|
||||
{ name: "Format", id: "format", type: "text" },
|
||||
{ name: "Genre", id: "genre", type: "text" },
|
||||
{ name: "Series", id: "series", type: "text" },
|
||||
{ name: "Volume", id: "volume", type: "text" },
|
||||
{ name: "Year", id: "year", type: "text" },
|
||||
{ name: "CoverURL", id: "coverURL", type: "text" },
|
||||
{ name: "Signed", id: "signed", type: "checkbox" },
|
||||
{ name: "Childrens", id: "childrens", type: "checkbox" },
|
||||
].reduce((acc, field) => {
|
||||
return acc.concat(
|
||||
`<label>${field.name} <input
|
||||
type="${field.type}"
|
||||
name="${field.name.toLowerCase()}"
|
||||
id="${field.id}"
|
||||
/></label><br/>`
|
||||
);
|
||||
}, "")}
|
||||
<input id="lookup" type="submit" value="look up">
|
||||
<input id="save" type="submit" value="save">
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
href="https://git.yetaga.in/alazyreader/library"
|
||||
>git</a
|
||||
>
|
||||
<a href="#" id="addBook" class="hidden">add book</a>
|
||||
<div id="searchBox">
|
||||
<label for="childrens" class="bookCount"
|
||||
>Include Childrens Books?</label
|
||||
|
@ -133,6 +133,10 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#header {
|
||||
height: 30px;
|
||||
width: calc(100vw - 20px);
|
||||
|
91
go.mod
91
go.mod
@ -1,10 +1,99 @@
|
||||
module git.yetaga.in/alazyreader/library
|
||||
|
||||
go 1.16
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.5
|
||||
|
||||
require (
|
||||
git.yetaga.in/alazyreader/go-openlibrary v0.0.1
|
||||
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
|
||||
)
|
||||
|
285
go.sum
285
go.sum
@ -1,65 +1,326 @@
|
||||
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=
|
||||
git.yetaga.in/alazyreader/go-openlibrary v0.0.1 h1:5juCi8d7YyNxXFvHytQNBww5E6GmPetM7nz3kVUqNQY=
|
||||
git.yetaga.in/alazyreader/go-openlibrary v0.0.1/go.mod h1:o6zBFJTovdFcpA+As1bRFvk5PDhAs2Lf8iVzUt7dKw8=
|
||||
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=
|
||||
|
13
query/amazon.go
Normal file
13
query/amazon.go
Normal file
@ -0,0 +1,13 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.yetaga.in/alazyreader/library/media"
|
||||
)
|
||||
|
||||
type Amazon struct{}
|
||||
|
||||
func (o *Amazon) GetByISBN(isbn string) (*media.Book, error) {
|
||||
return nil, fmt.Errorf("unimplemented")
|
||||
}
|
28
query/funcs.go
Normal file
28
query/funcs.go
Normal file
@ -0,0 +1,28 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func tryGetFirst(s []string) string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return s[0]
|
||||
}
|
||||
|
||||
func buildTitle(title, subtitle string) string {
|
||||
if subtitle != "" {
|
||||
return fmt.Sprintf("%s: %s", title, subtitle)
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
func getLastName(author string) string {
|
||||
names := strings.Split(author, " ")
|
||||
if len(names) < 2 {
|
||||
return author
|
||||
}
|
||||
return names[len(names)-1]
|
||||
}
|
158
query/googlebooks.go
Normal file
158
query/googlebooks.go
Normal file
@ -0,0 +1,158 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.yetaga.in/alazyreader/library/media"
|
||||
)
|
||||
|
||||
type GoogleBooks struct{}
|
||||
|
||||
type googleBookResult struct {
|
||||
Kind string `json:"kind"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
Items []item `json:"items"`
|
||||
}
|
||||
|
||||
type industryIdentifier struct {
|
||||
Type string `json:"type"`
|
||||
Identifier string `json:"identifier"`
|
||||
}
|
||||
|
||||
type readingMode struct {
|
||||
Text bool `json:"text"`
|
||||
Image bool `json:"image"`
|
||||
}
|
||||
|
||||
type panelizationSummary struct {
|
||||
ContainsEpubBubbles bool `json:"containsEpubBubbles"`
|
||||
ContainsImageBubbles bool `json:"containsImageBubbles"`
|
||||
}
|
||||
|
||||
type imageLink struct {
|
||||
SmallThumbnail string `json:"smallThumbnail"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
}
|
||||
|
||||
type volumeInfo struct {
|
||||
Title string `json:"title"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Authors []string `json:"authors"`
|
||||
Publisher string `json:"publisher"`
|
||||
PublishedDate string `json:"publishedDate"`
|
||||
Description string `json:"description"`
|
||||
IndustryIdentifiers []industryIdentifier `json:"industryIdentifiers"`
|
||||
ReadingModes readingMode `json:"readingModes"`
|
||||
PageCount int `json:"pageCount"`
|
||||
PrintType string `json:"printType"`
|
||||
Categories []string `json:"categories"`
|
||||
AverageRating float64 `json:"averageRating"`
|
||||
RatingsCount int `json:"ratingsCount"`
|
||||
MaturityRating string `json:"maturityRating"`
|
||||
AllowAnonLogging bool `json:"allowAnonLogging"`
|
||||
ContentVersion string `json:"contentVersion"`
|
||||
PanelizationSummary panelizationSummary `json:"panelizationSummary"`
|
||||
ImageLinks imageLink `json:"imageLinks"`
|
||||
Language string `json:"language"`
|
||||
PreviewLink string `json:"previewLink"`
|
||||
InfoLink string `json:"infoLink"`
|
||||
CanonicalVolumeLink string `json:"canonicalVolumeLink"`
|
||||
}
|
||||
|
||||
type saleInfo struct {
|
||||
Country string `json:"country"`
|
||||
Saleability string `json:"saleability"`
|
||||
IsEbook bool `json:"isEbook"`
|
||||
}
|
||||
|
||||
type epub struct {
|
||||
IsAvailable bool `json:"isAvailable"`
|
||||
}
|
||||
|
||||
type pdf struct {
|
||||
IsAvailable bool `json:"isAvailable"`
|
||||
}
|
||||
|
||||
type accessInfo struct {
|
||||
Country string `json:"country"`
|
||||
Viewability string `json:"viewability"`
|
||||
Embeddable bool `json:"embeddable"`
|
||||
PublicDomain bool `json:"publicDomain"`
|
||||
TextToSpeechPermission string `json:"textToSpeechPermission"`
|
||||
Epub epub `json:"epub"`
|
||||
Pdf pdf `json:"pdf"`
|
||||
WebReaderLink string `json:"webReaderLink"`
|
||||
AccessViewStatus string `json:"accessViewStatus"`
|
||||
QuoteSharingAllowed bool `json:"quoteSharingAllowed"`
|
||||
}
|
||||
|
||||
type searchInfo struct {
|
||||
TextSnippet string `json:"textSnippet"`
|
||||
}
|
||||
|
||||
type item struct {
|
||||
Kind string `json:"kind"`
|
||||
ID string `json:"id"`
|
||||
Etag string `json:"etag"`
|
||||
SelfLink string `json:"selfLink"`
|
||||
VolumeInfo volumeInfo `json:"volumeInfo"`
|
||||
SaleInfo saleInfo `json:"saleInfo"`
|
||||
AccessInfo accessInfo `json:"accessInfo"`
|
||||
SearchInfo searchInfo `json:"searchInfo"`
|
||||
}
|
||||
|
||||
func (g *GoogleBooks) GetByISBN(isbn string) (*media.Book, error) {
|
||||
client := &http.Client{}
|
||||
resp, err := client.Get(fmt.Sprintf("https://www.googleapis.com/books/v1/volumes?q=isbn:%s", isbn))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("received non-200 status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result googleBookResult
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(b, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result.Items) == 0 {
|
||||
return nil, fmt.Errorf("no book found")
|
||||
}
|
||||
|
||||
return googleToBook(result.Items[0]), nil
|
||||
}
|
||||
|
||||
func googleToBook(i item) *media.Book {
|
||||
return &media.Book{
|
||||
Title: buildTitle(i.VolumeInfo.Title, i.VolumeInfo.Subtitle),
|
||||
Authors: i.VolumeInfo.Authors,
|
||||
SortAuthor: strings.ToLower(getLastName(tryGetFirst(i.VolumeInfo.Authors))),
|
||||
ISBN10: getIdentifierType(i.VolumeInfo.IndustryIdentifiers, "ISBN_10"),
|
||||
ISBN13: getIdentifierType(i.VolumeInfo.IndustryIdentifiers, "ISBN_13"),
|
||||
Publisher: i.VolumeInfo.Publisher,
|
||||
Year: strings.Split(i.VolumeInfo.PublishedDate, "-")[0],
|
||||
Description: i.VolumeInfo.Description,
|
||||
Genre: tryGetFirst(i.VolumeInfo.Categories),
|
||||
}
|
||||
}
|
||||
|
||||
func getIdentifierType(iis []industryIdentifier, typename string) string {
|
||||
for _, ident := range iis {
|
||||
if ident.Type == typename {
|
||||
return ident.Identifier
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
11
query/null.go
Normal file
11
query/null.go
Normal file
@ -0,0 +1,11 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"git.yetaga.in/alazyreader/library/media"
|
||||
)
|
||||
|
||||
type Null struct{}
|
||||
|
||||
func (o *Null) GetByISBN(isbn string) (*media.Book, error) {
|
||||
return nil, nil
|
||||
}
|
46
query/openlibrary.go
Normal file
46
query/openlibrary.go
Normal file
@ -0,0 +1,46 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.yetaga.in/alazyreader/go-openlibrary/client"
|
||||
"git.yetaga.in/alazyreader/library/media"
|
||||
)
|
||||
|
||||
type OpenLibrary struct {
|
||||
client client.Client
|
||||
}
|
||||
|
||||
func (o *OpenLibrary) GetByISBN(isbn string) (*media.Book, error) {
|
||||
details, err := o.client.GetByISBN(isbn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return openLibraryToBook(details), nil
|
||||
}
|
||||
|
||||
func openLibraryToBook(details *client.BookDetails) *media.Book {
|
||||
return &media.Book{
|
||||
Title: details.Title,
|
||||
Authors: getAuthors(details.Authors),
|
||||
SortAuthor: strings.ToLower(getLastName(tryGetFirst(getAuthors(details.Authors)))),
|
||||
Publisher: getPublisher(details.Publishers),
|
||||
ISBN10: tryGetFirst(details.Identifiers.ISBN10),
|
||||
ISBN13: tryGetFirst(details.Identifiers.ISBN13),
|
||||
}
|
||||
}
|
||||
|
||||
func getPublisher(publishers []client.Publishers) string {
|
||||
if len(publishers) == 0 {
|
||||
return ""
|
||||
}
|
||||
return publishers[0].Name
|
||||
}
|
||||
|
||||
func getAuthors(authors []client.Authors) []string {
|
||||
ret := make([]string, len(authors))
|
||||
for _, author := range authors {
|
||||
ret = append(ret, author.Name)
|
||||
}
|
||||
return ret
|
||||
}
|
Loading…
Reference in New Issue
Block a user