management server listener #17
@@ -14,6 +14,7 @@ type Router struct {
 | 
				
			|||||||
	static  fs.FS
 | 
						static  fs.FS
 | 
				
			||||||
	lib     Library
 | 
						lib     Library
 | 
				
			||||||
	rcol    RecordCollection
 | 
						rcol    RecordCollection
 | 
				
			||||||
 | 
						query   Query
 | 
				
			||||||
	ts      *tailscale.LocalClient
 | 
						ts      *tailscale.LocalClient
 | 
				
			||||||
	isAdmin bool
 | 
						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[http.MethodDelete] = func() { deleteBook(router.lib, w, r) }
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		p.ServeHTTP(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:
 | 
						default:
 | 
				
			||||||
		static(router.static).ServeHTTP(w, r)
 | 
							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)
 | 
						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 {
 | 
					func static(f fs.FS) http.Handler {
 | 
				
			||||||
	return http.FileServer(http.FS(f))
 | 
						return http.FileServer(http.FS(f))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ import (
 | 
				
			|||||||
	"git.yetaga.in/alazyreader/library/database"
 | 
						"git.yetaga.in/alazyreader/library/database"
 | 
				
			||||||
	"git.yetaga.in/alazyreader/library/frontend"
 | 
						"git.yetaga.in/alazyreader/library/frontend"
 | 
				
			||||||
	"git.yetaga.in/alazyreader/library/media"
 | 
						"git.yetaga.in/alazyreader/library/media"
 | 
				
			||||||
 | 
						"git.yetaga.in/alazyreader/library/query"
 | 
				
			||||||
	"github.com/kelseyhightower/envconfig"
 | 
						"github.com/kelseyhightower/envconfig"
 | 
				
			||||||
	"golang.org/x/sync/errgroup"
 | 
						"golang.org/x/sync/errgroup"
 | 
				
			||||||
	"tailscale.com/tsnet"
 | 
						"tailscale.com/tsnet"
 | 
				
			||||||
@@ -35,6 +36,10 @@ type RecordCollection interface {
 | 
				
			|||||||
	GetAllRecords(context.Context) ([]media.Record, error)
 | 
						GetAllRecords(context.Context) ([]media.Record, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Query interface {
 | 
				
			||||||
 | 
						GetByISBN(string) (*media.Book, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	var c config.Config
 | 
						var c config.Config
 | 
				
			||||||
	must.Do(envconfig.Process("library", &c))
 | 
						must.Do(envconfig.Process("library", &c))
 | 
				
			||||||
@@ -55,6 +60,8 @@ func main() {
 | 
				
			|||||||
		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{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	staticRoot := must.Get(frontend.Root())
 | 
						staticRoot := must.Get(frontend.Root())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	servers := make(chan (*http.Server), 3)
 | 
						servers := make(chan (*http.Server), 3)
 | 
				
			||||||
@@ -74,6 +81,7 @@ func main() {
 | 
				
			|||||||
				static:  staticRoot,
 | 
									static:  staticRoot,
 | 
				
			||||||
				lib:     lib,
 | 
									lib:     lib,
 | 
				
			||||||
				rcol:    discogsCache,
 | 
									rcol:    discogsCache,
 | 
				
			||||||
 | 
									query:   queryProvider,
 | 
				
			||||||
				isAdmin: true,
 | 
									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) {
 | 
					func publicServer(port int, handler http.Handler) (*http.Server, net.Listener, error) {
 | 
				
			||||||
	server := &http.Server{Handler: handler}
 | 
						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 {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, nil, err
 | 
							return nil, nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log.Println("starting public server")
 | 
						log.Printf("public server: http://0.0.0.0:%d/", port)
 | 
				
			||||||
	return server, ln, nil
 | 
						return server, ln, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -163,6 +171,7 @@ func tailscaleListener(hostname string, handler *Router) (*http.Server, net.List
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, nil, err
 | 
							return nil, nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						log.Printf("management server: http://%s/", hostname)
 | 
				
			||||||
	server := &http.Server{Handler: handler}
 | 
						server := &http.Server{Handler: handler}
 | 
				
			||||||
	return server, ln, nil
 | 
						return server, ln, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,23 @@ var sortState = {
 | 
				
			|||||||
  sortOrder: "asc",
 | 
					  sortOrder: "asc",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var admin = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function init() {
 | 
					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")
 | 
					  fetch("/api/books")
 | 
				
			||||||
    .then((response) => response.json())
 | 
					    .then((response) => response.json())
 | 
				
			||||||
    .then((books) => {
 | 
					    .then((books) => {
 | 
				
			||||||
@@ -33,6 +49,10 @@ function init() {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function renderAddBookView() {
 | 
				
			||||||
 | 
					  document.getElementById("current").innerHTML = AddBookTemplate();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renderTable(books, sortField) {
 | 
					function renderTable(books, sortField) {
 | 
				
			||||||
  if (sortField) {
 | 
					  if (sortField) {
 | 
				
			||||||
    if (sortState.sortBy === sortField && sortState.sortOrder === "asc") {
 | 
					    if (sortState.sortBy === sortField && sortState.sortOrder === "asc") {
 | 
				
			||||||
@@ -173,6 +193,7 @@ function BookTemplate({
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    ${signed ? "<span>Signed by the author ✒</span><br/>" : ""}
 | 
					    ${signed ? "<span>Signed by the author ✒</span><br/>" : ""}
 | 
				
			||||||
    <span>${format}</span>
 | 
					    <span>${format}</span>
 | 
				
			||||||
 | 
					    ${admin ? `<a href="#">Edit Book</a>` : ""}
 | 
				
			||||||
  </div>`;
 | 
					  </div>`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -211,3 +232,31 @@ function TableTemplate(books) {
 | 
				
			|||||||
      return acc.concat(TableRowTemplate(book));
 | 
					      return acc.concat(TableRowTemplate(book));
 | 
				
			||||||
    }, "")} </table>`;
 | 
					    }, "")} </table>`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function AddBookTemplate() {
 | 
				
			||||||
 | 
					  return `<div class="addBookView">
 | 
				
			||||||
 | 
					    <form>${[
 | 
				
			||||||
 | 
					      { 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(
 | 
				
			||||||
 | 
					        `<label>${field.name} <input type="${field.type}" name="${field.name.toLowerCase}"/></label><br/>`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }, "")}
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					  </div>`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,7 @@
 | 
				
			|||||||
          href="https://git.yetaga.in/alazyreader/library"
 | 
					          href="https://git.yetaga.in/alazyreader/library"
 | 
				
			||||||
          >git</a
 | 
					          >git</a
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					        <a href="#" id="addBook" class="hidden">add book</a>
 | 
				
			||||||
        <div id="searchBox">
 | 
					        <div id="searchBox">
 | 
				
			||||||
          <label for="childrens" class="bookCount"
 | 
					          <label for="childrens" class="bookCount"
 | 
				
			||||||
            >Include Childrens Books?</label
 | 
					            >Include Childrens Books?</label
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -185,7 +185,7 @@ body {
 | 
				
			|||||||
  padding-left: 20px;
 | 
					  padding-left: 20px;
 | 
				
			||||||
  padding-right: 20px;
 | 
					  padding-right: 20px;
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  justify-content: space-evenly; 
 | 
					  justify-content: space-evenly;
 | 
				
			||||||
  align-items: flex-start;
 | 
					  align-items: flex-start;
 | 
				
			||||||
  flex-wrap: wrap;
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
  overflow: auto;
 | 
					  overflow: auto;
 | 
				
			||||||
@@ -235,4 +235,4 @@ footer {
 | 
				
			|||||||
  width: calc(100% - 40px);
 | 
					  width: calc(100% - 40px);
 | 
				
			||||||
  color: #a29c77;
 | 
					  color: #a29c77;
 | 
				
			||||||
  border-top: 2px solid #d8d0a0;
 | 
					  border-top: 2px solid #d8d0a0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -133,6 +133,10 @@ body {
 | 
				
			|||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.hidden {
 | 
				
			||||||
 | 
					  display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#header {
 | 
					#header {
 | 
				
			||||||
  height: 30px;
 | 
					  height: 30px;
 | 
				
			||||||
  width: calc(100vw - 20px);
 | 
					  width: calc(100vw - 20px);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@@ -15,6 +15,7 @@ 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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
 | 
				
			|||||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
 | 
					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 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
 | 
				
			||||||
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
 | 
					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 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
 | 
				
			||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
					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 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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       int                  `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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user