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 +