management server listener #17
@@ -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))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 ? "<span>Signed by the author ✒</span><br/>" : ""}
 | 
			
		||||
    <span>${format}</span>
 | 
			
		||||
    ${admin ? `<a href="#">Edit Book</a>` : ""}
 | 
			
		||||
  </div>`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -211,3 +232,31 @@ function TableTemplate(books) {
 | 
			
		||||
      return acc.concat(TableRowTemplate(book));
 | 
			
		||||
    }, "")} </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"
 | 
			
		||||
          >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);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@@ -15,6 +15,7 @@ require (
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	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/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // 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/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=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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