management server listener #17
@@ -2,8 +2,10 @@ package main
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/fs"
 | 
						"io/fs"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.yetaga.in/alazyreader/library/media"
 | 
						"git.yetaga.in/alazyreader/library/media"
 | 
				
			||||||
@@ -30,6 +32,7 @@ func (h path) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func writeJSONerror(w http.ResponseWriter, err string, status int) {
 | 
					func writeJSONerror(w http.ResponseWriter, err string, status int) {
 | 
				
			||||||
 | 
						log.Println(err)
 | 
				
			||||||
	writeJSON(w, struct{ Status, Reason string }{Status: "error", Reason: err}, status)
 | 
						writeJSON(w, struct{ Status, Reason string }{Status: "error", Reason: err}, status)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -97,50 +100,26 @@ func getBooks(l Library, w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func addBook(l Library, w http.ResponseWriter, r *http.Request) {
 | 
					func addBook(l Library, w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	if r.Body == nil {
 | 
						book, err := ReadBody[media.Book](r.Body)
 | 
				
			||||||
		writeJSONerror(w, "no body provided", http.StatusBadRequest)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							writeJSONerror(w, err.Error(), http.StatusBadRequest)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer r.Body.Close()
 | 
						if err = l.AddBook(r.Context(), book); err != nil {
 | 
				
			||||||
	b, err := io.ReadAll(r.Body)
 | 
							writeJSONerror(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		writeJSONerror(w, "error reading body", http.StatusBadRequest)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	book := &media.Book{}
 | 
					 | 
				
			||||||
	err = json.Unmarshal(b, book)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		writeJSONerror(w, "error parsing body", http.StatusBadRequest)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = l.AddBook(r.Context(), book)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		writeJSONerror(w, "error parsing body", http.StatusBadRequest)
 | 
					 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	w.WriteHeader(http.StatusAccepted)
 | 
						w.WriteHeader(http.StatusAccepted)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func deleteBook(l Library, w http.ResponseWriter, r *http.Request) {
 | 
					func deleteBook(l Library, w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	if r.Body == nil {
 | 
						book, err := ReadBody[media.Book](r.Body)
 | 
				
			||||||
		writeJSONerror(w, "no body provided", http.StatusBadRequest)
 | 
						if err != nil {
 | 
				
			||||||
 | 
							writeJSONerror(w, err.Error(), http.StatusBadRequest)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer r.Body.Close()
 | 
						if err = l.DeleteBook(r.Context(), book); err != nil {
 | 
				
			||||||
	b, err := io.ReadAll(r.Body)
 | 
							writeJSONerror(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		writeJSONerror(w, "error reading body", http.StatusBadRequest)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	book := &media.Book{}
 | 
					 | 
				
			||||||
	err = json.Unmarshal(b, book)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		writeJSONerror(w, "error parsing body", http.StatusBadRequest)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = l.DeleteBook(r.Context(), book)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		writeJSONerror(w, "error deleting book", http.StatusInternalServerError)
 | 
					 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	w.WriteHeader(http.StatusAccepted)
 | 
						w.WriteHeader(http.StatusAccepted)
 | 
				
			||||||
@@ -165,12 +144,12 @@ func getWhoAmI(ts *tailscale.LocalClient, w http.ResponseWriter, r *http.Request
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func lookupBook(query Query, w http.ResponseWriter, r *http.Request) {
 | 
					func lookupBook(query Query, w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	isbn := r.FormValue("isbn")
 | 
						req, err := ReadBody[media.Book](r.Body)
 | 
				
			||||||
	if len(isbn) != 10 && len(isbn) != 13 {
 | 
						if err != nil {
 | 
				
			||||||
		writeJSONerror(w, "invalid isbn", http.StatusBadRequest)
 | 
							writeJSONerror(w, err.Error(), http.StatusBadRequest)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	book, err := query.GetByISBN(isbn)
 | 
						book, err := query.GetByISBN(req.ISBN13)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		writeJSONerror(w, err.Error(), http.StatusInternalServerError)
 | 
							writeJSONerror(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -181,3 +160,20 @@ func lookupBook(query Query, w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
func static(f fs.FS) http.Handler {
 | 
					func static(f fs.FS) http.Handler {
 | 
				
			||||||
	return http.FileServer(http.FS(f))
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,30 +45,26 @@ func main() {
 | 
				
			|||||||
	must.Do(envconfig.Process("library", &c))
 | 
						must.Do(envconfig.Process("library", &c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var lib Library
 | 
						var lib Library
 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	if c.DBType == "memory" {
 | 
						if c.DBType == "memory" {
 | 
				
			||||||
		lib = &database.Memory{}
 | 
							lib = &database.Memory{}
 | 
				
			||||||
	} else if c.DBType == "sql" {
 | 
						} else if c.DBType == "sql" {
 | 
				
			||||||
		var latest, run int
 | 
							sqllib, latest, run, err := setupSQL(c)
 | 
				
			||||||
		lib, latest, run, err = setupSQL(c)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Fatalf("err starting sql connection: %v", err)
 | 
								log.Fatalf("sql connection err: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		log.Printf("latest migration: %d; migrations run: %d", latest, run)
 | 
							log.Printf("latest migration: %d; migrations run: %d", latest, run)
 | 
				
			||||||
 | 
							lib = sqllib
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	discogsCache := must.Get(database.NewDiscogsCache(
 | 
						discogsCache := must.Get(database.NewDiscogsCache(
 | 
				
			||||||
		c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile,
 | 
							c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile,
 | 
				
			||||||
	))
 | 
						))
 | 
				
			||||||
 | 
					 | 
				
			||||||
	queryProvider := &query.GoogleBooks{}
 | 
						queryProvider := &query.GoogleBooks{}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	staticRoot := must.Get(frontend.Root())
 | 
						staticRoot := must.Get(frontend.Root())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	servers := make(chan (*http.Server), 3)
 | 
						servers := make(chan (*http.Server), 3)
 | 
				
			||||||
	errGroup := errgroup.Group{}
 | 
						errGroup := errgroup.Group{}
 | 
				
			||||||
	errGroup.Go(func() error {
 | 
						errGroup.Go(func() error {
 | 
				
			||||||
		return start(servers)(
 | 
							return start(servers)(publicServer(8080, &Router{
 | 
				
			||||||
			publicServer(8080, &Router{
 | 
					 | 
				
			||||||
			static:  staticRoot,
 | 
								static:  staticRoot,
 | 
				
			||||||
			lib:     lib,
 | 
								lib:     lib,
 | 
				
			||||||
			rcol:    discogsCache,
 | 
								rcol:    discogsCache,
 | 
				
			||||||
@@ -76,8 +72,7 @@ func main() {
 | 
				
			|||||||
		}))
 | 
							}))
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	errGroup.Go(func() error {
 | 
						errGroup.Go(func() error {
 | 
				
			||||||
		return start(servers)(
 | 
							return start(servers)(tailscaleListener("library-admin", &Router{
 | 
				
			||||||
			tailscaleListener("library-admin", &Router{
 | 
					 | 
				
			||||||
			static:  staticRoot,
 | 
								static:  staticRoot,
 | 
				
			||||||
			lib:     lib,
 | 
								lib:     lib,
 | 
				
			||||||
			rcol:    discogsCache,
 | 
								rcol:    discogsCache,
 | 
				
			||||||
@@ -88,7 +83,6 @@ func main() {
 | 
				
			|||||||
	errGroup.Go(func() error {
 | 
						errGroup.Go(func() error {
 | 
				
			||||||
		return shutdown(servers)
 | 
							return shutdown(servers)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.Println(errGroup.Wait())
 | 
						log.Println(errGroup.Wait())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -172,6 +166,5 @@ func tailscaleListener(hostname string, handler *Router) (*http.Server, net.List
 | 
				
			|||||||
		return nil, nil, err
 | 
							return nil, nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log.Printf("management server: http://%s/", hostname)
 | 
						log.Printf("management server: http://%s/", hostname)
 | 
				
			||||||
	server := &http.Server{Handler: handler}
 | 
						return &http.Server{Handler: handler}, ln, nil
 | 
				
			||||||
	return server, ln, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,6 +51,62 @@ function init() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function renderAddBookView() {
 | 
					function renderAddBookView() {
 | 
				
			||||||
  document.getElementById("current").innerHTML = AddBookTemplate();
 | 
					  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 {
 | 
				
			||||||
 | 
					      console.log("no isbn");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  document.getElementById("save").addEventListener("click", (e) => {
 | 
				
			||||||
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					    fetch("/api/books", {
 | 
				
			||||||
 | 
					      method: "POST",
 | 
				
			||||||
 | 
					      headers: { "Content-Type": "application/json" },
 | 
				
			||||||
 | 
					      body: JSON.stringify({
 | 
				
			||||||
 | 
					        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,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    renderAddBookView();
 | 
				
			||||||
 | 
					    init();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renderTable(books, sortField) {
 | 
					function renderTable(books, sortField) {
 | 
				
			||||||
@@ -94,7 +150,8 @@ function renderTable(books, sortField) {
 | 
				
			|||||||
  Array.from(bookElement.querySelectorAll("tbody tr th[data-sort-by]")).forEach(
 | 
					  Array.from(bookElement.querySelectorAll("tbody tr th[data-sort-by]")).forEach(
 | 
				
			||||||
    (row) => {
 | 
					    (row) => {
 | 
				
			||||||
      row.addEventListener("click", function (e) {
 | 
					      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(books, e.target.dataset.sortBy);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@@ -170,9 +227,7 @@ function BookTemplate({
 | 
				
			|||||||
  "isbn-10": isbn10,
 | 
					  "isbn-10": isbn10,
 | 
				
			||||||
  authors,
 | 
					  authors,
 | 
				
			||||||
  coverURL,
 | 
					  coverURL,
 | 
				
			||||||
  description,
 | 
					 | 
				
			||||||
  format,
 | 
					  format,
 | 
				
			||||||
  notes,
 | 
					 | 
				
			||||||
  publisher,
 | 
					  publisher,
 | 
				
			||||||
  series,
 | 
					  series,
 | 
				
			||||||
  signed,
 | 
					  signed,
 | 
				
			||||||
@@ -235,28 +290,33 @@ function TableTemplate(books) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function AddBookTemplate() {
 | 
					function AddBookTemplate() {
 | 
				
			||||||
  return `<div class="addBookView">
 | 
					  return `<div class="addBookView">
 | 
				
			||||||
    <form>${[
 | 
					    <div id="newBookForm">
 | 
				
			||||||
      { name: "ISBN10", type: "text" },
 | 
					      ${[
 | 
				
			||||||
      { name: "ISBN13", type: "text" },
 | 
					        { name: "Title", id: "title", type: "text" },
 | 
				
			||||||
      { name: "Title", type: "text" },
 | 
					        { name: "Authors", id: "authors", type: "text" },
 | 
				
			||||||
      { name: "Authors", type: "text" },
 | 
					        { name: "SortAuthor", id: "sortAuthor", type: "text" },
 | 
				
			||||||
      { name: "SortAuthor", type: "text" },
 | 
					        { name: "ISBN10", id: "isbn-10", type: "text" },
 | 
				
			||||||
      { name: "Format", type: "text" },
 | 
					        { name: "ISBN13", id: "isbn-13", type: "text" },
 | 
				
			||||||
      { name: "Genre", type: "text" },
 | 
					        { name: "Publisher", id: "publisher", type: "text" },
 | 
				
			||||||
      { name: "Publisher", type: "text" },
 | 
					        { name: "Format", id: "format", type: "text" },
 | 
				
			||||||
      { name: "Series", type: "text" },
 | 
					        { name: "Genre", id: "genre", type: "text" },
 | 
				
			||||||
      { name: "Volume", type: "text" },
 | 
					        { name: "Series", id: "series", type: "text" },
 | 
				
			||||||
      { name: "Year", type: "text" },
 | 
					        { name: "Volume", id: "volume", type: "text" },
 | 
				
			||||||
      { name: "Signed", type: "checkbox" },
 | 
					        { name: "Year", id: "year", type: "text" },
 | 
				
			||||||
      // { name: "Description", type: "text" },
 | 
					        { name: "CoverURL", id: "coverURL", type: "text" },
 | 
				
			||||||
      // { name: "Notes", type: "text" },
 | 
					        { name: "Signed", id: "signed", type: "checkbox" },
 | 
				
			||||||
      { name: "CoverURL", type: "text" },
 | 
					        { name: "Childrens", id: "childrens", type: "checkbox" },
 | 
				
			||||||
      { name: "Childrens", type: "checkbox" },
 | 
					 | 
				
			||||||
      ].reduce((acc, field) => {
 | 
					      ].reduce((acc, field) => {
 | 
				
			||||||
        return acc.concat(
 | 
					        return acc.concat(
 | 
				
			||||||
        `<label>${field.name} <input type="${field.type}" name="${field.name.toLowerCase}"/></label><br/>`
 | 
					          `<label>${field.name} <input
 | 
				
			||||||
 | 
					              type="${field.type}"
 | 
				
			||||||
 | 
					              name="${field.name.toLowerCase()}"
 | 
				
			||||||
 | 
					              id="${field.id}"
 | 
				
			||||||
 | 
					            /></label><br/>`
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      }, "")}
 | 
					      }, "")}
 | 
				
			||||||
    </form>
 | 
					      <input id="lookup" type="submit" value="look up">
 | 
				
			||||||
 | 
					      <input id="save" type="submit" value="save">
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
  </div>`;
 | 
					  </div>`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@@ -5,6 +5,7 @@ go 1.21
 | 
				
			|||||||
toolchain go1.21.5
 | 
					toolchain go1.21.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
 | 
						git.yetaga.in/alazyreader/go-openlibrary v0.0.1
 | 
				
			||||||
	github.com/gdamore/tcell/v2 v2.7.0
 | 
						github.com/gdamore/tcell/v2 v2.7.0
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.7.1
 | 
						github.com/go-sql-driver/mysql v1.7.1
 | 
				
			||||||
	github.com/irlndts/go-discogs v0.3.6
 | 
						github.com/irlndts/go-discogs v0.3.6
 | 
				
			||||||
@@ -15,7 +16,6 @@ require (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	filippo.io/edwards25519 v1.0.0 // indirect
 | 
						filippo.io/edwards25519 v1.0.0 // indirect
 | 
				
			||||||
	git.yetaga.in/alazyreader/go-openlibrary v0.0.1 // indirect
 | 
					 | 
				
			||||||
	github.com/akutz/memconn v0.1.0 // indirect
 | 
						github.com/akutz/memconn v0.1.0 // indirect
 | 
				
			||||||
	github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // 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 v1.21.0 // indirect
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,7 +50,7 @@ type volumeInfo struct {
 | 
				
			|||||||
	PageCount           int                  `json:"pageCount"`
 | 
						PageCount           int                  `json:"pageCount"`
 | 
				
			||||||
	PrintType           string               `json:"printType"`
 | 
						PrintType           string               `json:"printType"`
 | 
				
			||||||
	Categories          []string             `json:"categories"`
 | 
						Categories          []string             `json:"categories"`
 | 
				
			||||||
	AverageRating       int                  `json:"averageRating"`
 | 
						AverageRating       float64              `json:"averageRating"`
 | 
				
			||||||
	RatingsCount        int                  `json:"ratingsCount"`
 | 
						RatingsCount        int                  `json:"ratingsCount"`
 | 
				
			||||||
	MaturityRating      string               `json:"maturityRating"`
 | 
						MaturityRating      string               `json:"maturityRating"`
 | 
				
			||||||
	AllowAnonLogging    bool                 `json:"allowAnonLogging"`
 | 
						AllowAnonLogging    bool                 `json:"allowAnonLogging"`
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user