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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user