Switch from open library API to Inventaire API
This commit is contained in:
@@ -107,6 +107,7 @@
|
|||||||
<h3 class="subtitle">{{data.author}}</h3>
|
<h3 class="subtitle">{{data.author}}</h3>
|
||||||
<p>{{data.summary}}</p>
|
<p>{{data.summary}}</p>
|
||||||
<div class="my-5" v-if="data.isbn">ISBN: {{data.isbn}}</div>
|
<div class="my-5" v-if="data.isbn">ISBN: {{data.isbn}}</div>
|
||||||
|
<div class="my-5" v-if="data.inventaireid">Inventaire ID: {{data.inventaireid}}</div>
|
||||||
<div class="my-5" v-if="data.openlibraryid">OLID: {{data.openlibraryid}}</div>
|
<div class="my-5" v-if="data.openlibraryid">OLID: {{data.openlibraryid}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: Number,
|
id: Number,
|
||||||
openlibraryid: String,
|
inventaireid: String,
|
||||||
title: String,
|
title: String,
|
||||||
author: String,
|
author: String,
|
||||||
rating: Number,
|
rating: Number,
|
||||||
@@ -31,7 +31,7 @@ function openBook() {
|
|||||||
if (props.id != 0) {
|
if (props.id != 0) {
|
||||||
router.push(`/book/${props.id}`);
|
router.push(`/book/${props.id}`);
|
||||||
} else {
|
} else {
|
||||||
router.push(`/importopenlibrary/${props.openlibraryid}`)
|
router.push(`/importinventaire/${props.inventaireid}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
openlibraryid: String
|
inventaireid: String
|
||||||
});
|
});
|
||||||
const error = ref(null);
|
const error = ref(null);
|
||||||
const data = ref(null);
|
const data = ref(null);
|
||||||
|
|
||||||
async function importOpenLibraryId() {
|
async function importInventaireId() {
|
||||||
const res = await postImportBook(props.openlibraryid);
|
const res = await postImportBook(props.inventaireid, navigator.language.substring(0,2));
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
router.push(`/book/${json.id}`);
|
router.push(`/book/${json.id}`);
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
importOpenLibraryId();
|
importInventaireId();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="error">Importing {{props.openlibraryid}}...</div>
|
<div v-if="error">{{error}}</div>
|
||||||
<div v-else-if="!data">Importing {{props.openlibraryid}}...</div>
|
<div v-else-if="!data">Importing {{props.inventaireid}}...</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
if (typeof searchterm.value === "undefined" || searchterm.value === "") {
|
if (typeof searchterm.value === "undefined" || searchterm.value === "") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
router.push('/search/' + encodeURIComponent(searchterm.value));
|
router.push('/search/' + searchterm.value);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ export function postBook(book) {
|
|||||||
return genericPayloadCall('/book', book.value, 'POST')
|
return genericPayloadCall('/book', book.value, 'POST')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postImportBook(id) {
|
export async function postImportBook(id, language) {
|
||||||
return genericPayloadCall('/importbook', {openlibraryid: id}, 'POST');
|
return genericPayloadCall('/importbook', {inventaireid: id, lang: language}, 'POST');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function putReadBook(bookId) {
|
export async function putReadBook(bookId) {
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import SignUp from './SignUp.vue'
|
|||||||
import LogIn from './LogIn.vue'
|
import LogIn from './LogIn.vue'
|
||||||
import Home from './Home.vue'
|
import Home from './Home.vue'
|
||||||
import SearchBook from './SearchBook.vue'
|
import SearchBook from './SearchBook.vue'
|
||||||
import OpenLibraryImport from './OpenLibraryImport.vue'
|
import InventaireImport from './InventaireImport.vue'
|
||||||
import { useAuthStore } from './auth.store'
|
import { useAuthStore } from './auth.store'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', component: Home },
|
{ path: '/', component: Home },
|
||||||
{ path: '/books', component: BooksBrowser },
|
{ path: '/books', component: BooksBrowser },
|
||||||
{ path: '/book/:id', component: BookForm, props: true },
|
{ path: '/book/:id', component: BookForm, props: true },
|
||||||
{ path: '/importopenlibrary/:openlibraryid', component: OpenLibraryImport, props: true },
|
{ path: '/importinventaire/:inventaireid', component: InventaireImport, props: true },
|
||||||
{ path: '/author/:id', component: AuthorForm, props: true },
|
{ path: '/author/:id', component: AuthorForm, props: true },
|
||||||
{ path: '/search/:searchterm', component: SearchBook, props: true },
|
{ path: '/search/:searchterm', component: SearchBook, props: true },
|
||||||
{ path: '/add', component: AddBook },
|
{ path: '/add', component: AddBook },
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type fetchedBook struct {
|
|||||||
Title string `json:"title" binding:"required,max=300"`
|
Title string `json:"title" binding:"required,max=300"`
|
||||||
Author string `json:"author" binding:"max=100"`
|
Author string `json:"author" binding:"max=100"`
|
||||||
ISBN string `json:"isbn"`
|
ISBN string `json:"isbn"`
|
||||||
|
InventaireID string `json:"inventaireid"`
|
||||||
OpenLibraryId string `json:"openlibraryid"`
|
OpenLibraryId string `json:"openlibraryid"`
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
Rating int `json:"rating"`
|
Rating int `json:"rating"`
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ type hasId struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPostImportBookHandler_Ok(t *testing.T) {
|
func TestPostImportBookHandler_Ok(t *testing.T) {
|
||||||
id := testPostImportBookHandler(t, "OL21177W", http.StatusOK)
|
id := testPostImportBookHandler(t, "Q202975", http.StatusOK)
|
||||||
book := testGetBook(t, strconv.FormatUint(uint64(id), 10), 200)
|
book := testGetBook(t, strconv.FormatUint(uint64(id), 10), 200)
|
||||||
assert.Equal(t, "Wuthering Heights", book.Title)
|
assert.Equal(t, "Les Hauts de Hurlevent", book.Title)
|
||||||
assert.Equal(t, "Emily Brontë", book.Author)
|
assert.Equal(t, "Emily Brontë", book.Author)
|
||||||
assert.Equal(t, "OL21177W", book.OpenLibraryId)
|
assert.Equal(t, "Q202975", book.InventaireID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPostImportBookHandler_OkAuthorKey(t *testing.T) {
|
func TestPostImportBookHandler_OkAuthorKey(t *testing.T) {
|
||||||
id := testPostImportBookHandler(t, "OL7525169M", http.StatusOK)
|
id := testPostImportBookHandler(t, "Q1317915", http.StatusOK)
|
||||||
book := testGetBook(t, strconv.FormatUint(uint64(id), 10), 200)
|
book := testGetBook(t, strconv.FormatUint(uint64(id), 10), 200)
|
||||||
assert.Equal(t, "Dr. Bloodmoney, or How We Got Along After the Bomb", book.Title)
|
assert.Equal(t, "Dr Bloodmoney", book.Title)
|
||||||
assert.Equal(t, "Philip K. Dick", book.Author)
|
assert.Equal(t, "Philip K. Dick", book.Author)
|
||||||
assert.Equal(t, "OL7525169M", book.OpenLibraryId)
|
assert.Equal(t, "Q1317915", book.InventaireID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPostImportBookHandler_NoOLID(t *testing.T) {
|
func TestPostImportBookHandler_NoOLID(t *testing.T) {
|
||||||
@@ -43,7 +43,8 @@ func testPostImportBookHandler(t *testing.T, openlibraryid string, expectedCode
|
|||||||
|
|
||||||
token := testutils.ConnectDemoUser(router)
|
token := testutils.ConnectDemoUser(router)
|
||||||
queryJson := `{
|
queryJson := `{
|
||||||
"openlibraryid":"%s"
|
"inventaireid":"%s",
|
||||||
|
"lang":"fr"
|
||||||
}`
|
}`
|
||||||
queryJson = fmt.Sprintf(queryJson, openlibraryid)
|
queryJson = fmt.Sprintf(queryJson, openlibraryid)
|
||||||
req, _ := http.NewRequest("POST", "/importbook",
|
req, _ := http.NewRequest("POST", "/importbook",
|
||||||
|
|||||||
66
internal/callapiutils/callapiutils.go
Normal file
66
internal/callapiutils/callapiutils.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package callapiutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddQueryParamInt(u *url.URL, paramName string, paramValue int) {
|
||||||
|
AddQueryParam(u, paramName, strconv.Itoa(paramValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddQueryParam(u *url.URL, paramName string, paramValue string) {
|
||||||
|
q := u.Query()
|
||||||
|
q.Set(paramName, paramValue)
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchAndParseResult[T any](u *url.URL, queryResult *T) error {
|
||||||
|
resp, err := DoApiQuery(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
bodyError, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Call to %s returned code %d:\n%s", u.String(), resp.StatusCode, string(bodyError))
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(resp.Body)
|
||||||
|
err = decoder.Decode(queryResult)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoApiQuery(u *url.URL) (*http.Response, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
|
req.Header.Add("User-Agent", "PersonalLibraryManager/0.1 (artlef@protonmail.com)")
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ComputeUrl(baseUrl string, paths ...string) (*url.URL, error) {
|
||||||
|
u, err := url.Parse(baseUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, p := range paths {
|
||||||
|
u = u.JoinPath(p)
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
41
internal/inventaire/inventaire.go
Normal file
41
internal/inventaire/inventaire.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package inventaire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"git.artlef.fr/PersonalLibraryManager/internal/callapiutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InventaireSearchResult struct {
|
||||||
|
Results []InventaireSearchBook `json:"results"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InventaireSearchBook struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeInventaireUrl(paths ...string) (*url.URL, error) {
|
||||||
|
baseUrl := "https://inventaire.io/api"
|
||||||
|
return callapiutils.ComputeUrl(baseUrl, paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallInventaireSearch(searchterm string, limit int, offset int) (InventaireSearchResult, error) {
|
||||||
|
var queryResult InventaireSearchResult
|
||||||
|
u, err := computeInventaireUrl("search")
|
||||||
|
if err != nil {
|
||||||
|
return queryResult, err
|
||||||
|
}
|
||||||
|
if limit != 0 {
|
||||||
|
callapiutils.AddQueryParamInt(u, "limit", limit)
|
||||||
|
}
|
||||||
|
if offset != 0 {
|
||||||
|
callapiutils.AddQueryParamInt(u, "offset", offset)
|
||||||
|
}
|
||||||
|
callapiutils.AddQueryParam(u, "types", "works")
|
||||||
|
callapiutils.AddQueryParam(u, "search", searchterm)
|
||||||
|
err = callapiutils.FetchAndParseResult(u, &queryResult)
|
||||||
|
return queryResult, err
|
||||||
|
}
|
||||||
46
internal/inventaire/inventaire_test.go
Normal file
46
internal/inventaire/inventaire_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package inventaire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCallInventaireSearch_NoLimit(t *testing.T) {
|
||||||
|
result, err := CallInventaireSearch("salammbo", 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 17, result.Total)
|
||||||
|
assert.Equal(t, 10, len(result.Results))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallInventaireSearch_Limit(t *testing.T) {
|
||||||
|
result, err := CallInventaireSearch("salammbo", 5, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 17, result.Total)
|
||||||
|
assert.Equal(t, 5, len(result.Results))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallInventaireSearch_Offset(t *testing.T) {
|
||||||
|
result, err := CallInventaireSearch("salammbo", 0, 15)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 17, result.Total)
|
||||||
|
assert.Equal(t, 2, len(result.Results))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallInventaireBook_BraveNewWorld(t *testing.T) {
|
||||||
|
result, err := CallInventaireBook("Q191949", "fr")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "Le Meilleur des mondes", result.Title)
|
||||||
|
assert.Equal(t, "roman de Aldous Huxley", result.Description)
|
||||||
|
assert.Equal(t, "Q81447", result.Author.ID)
|
||||||
|
assert.Equal(t, "Aldous Huxley", result.Author.Name)
|
||||||
|
assert.Equal(t, "écrivain, romancier et philosophe britannique (1894–1963)", result.Author.Description)
|
||||||
|
}
|
||||||
102
internal/inventaire/inventairebook.go
Normal file
102
internal/inventaire/inventairebook.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package inventaire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.artlef.fr/PersonalLibraryManager/internal/callapiutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InventaireBookResult struct {
|
||||||
|
ID string
|
||||||
|
Lang string
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Author *InventaireAuthorResult
|
||||||
|
}
|
||||||
|
|
||||||
|
type InventaireAuthorResult struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InventaireBookResult) UnmarshalJSON(b []byte) error {
|
||||||
|
var parsed struct {
|
||||||
|
Entities map[string]json.RawMessage
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entity := range parsed.Entities {
|
||||||
|
|
||||||
|
var inventaireEntity struct {
|
||||||
|
WdId string `json:"wdId"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Labels map[string]json.RawMessage `json:"labels"`
|
||||||
|
Descriptions map[string]json.RawMessage `json:"descriptions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(entity, &inventaireEntity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inventaireEntity.WdId == i.ID {
|
||||||
|
title, err := findLangageField(inventaireEntity.Labels, i.Lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Title = title
|
||||||
|
description, err := findLangageField(inventaireEntity.Descriptions, i.Lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Description = description
|
||||||
|
} else if inventaireEntity.Type == "human" {
|
||||||
|
a := InventaireAuthorResult{ID: inventaireEntity.WdId}
|
||||||
|
name, err := findLangageField(inventaireEntity.Labels, i.Lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.Name = name
|
||||||
|
desc, err := findLangageField(inventaireEntity.Descriptions, i.Lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.Description = desc
|
||||||
|
i.Author = &a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func findLangageField(multipleMessageFields map[string]json.RawMessage, lang string) (string, error) {
|
||||||
|
fieldToParse, ok := multipleMessageFields[lang]
|
||||||
|
if ok {
|
||||||
|
var parsedField string
|
||||||
|
err := json.Unmarshal(fieldToParse, &parsedField)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return parsedField, err
|
||||||
|
} else {
|
||||||
|
return "", errors.New("multi lang field could not be parsed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallInventaireBook(inventaireId string, lang string) (InventaireBookResult, error) {
|
||||||
|
queryResult := InventaireBookResult{ID: inventaireId, Lang: lang}
|
||||||
|
u, err := computeInventaireUrl("entities")
|
||||||
|
if err != nil {
|
||||||
|
return queryResult, err
|
||||||
|
}
|
||||||
|
callapiutils.AddQueryParam(u, "action", "by-uris")
|
||||||
|
callapiutils.AddQueryParam(u, "relatives", "wdt:P50")
|
||||||
|
callapiutils.AddQueryParam(u, "lang", lang)
|
||||||
|
callapiutils.AddQueryParam(u, "uris", "wd:"+inventaireId)
|
||||||
|
|
||||||
|
err = callapiutils.FetchAndParseResult(u, &queryResult)
|
||||||
|
return queryResult, err
|
||||||
|
}
|
||||||
@@ -6,5 +6,6 @@ type Author struct {
|
|||||||
gorm.Model
|
gorm.Model
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
|
InventaireID string
|
||||||
OpenLibraryId string
|
OpenLibraryId string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ type Book struct {
|
|||||||
gorm.Model
|
gorm.Model
|
||||||
Title string `json:"title" gorm:"not null"`
|
Title string `json:"title" gorm:"not null"`
|
||||||
ISBN string `json:"isbn"`
|
ISBN string `json:"isbn"`
|
||||||
|
InventaireID string `json:"inventaireid"`
|
||||||
OpenLibraryId string `json:"openlibraryid"`
|
OpenLibraryId string `json:"openlibraryid"`
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
Author Author
|
Author Author
|
||||||
|
|||||||
@@ -1,53 +1,12 @@
|
|||||||
package openlibrary
|
package openlibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
|
"git.artlef.fr/PersonalLibraryManager/internal/callapiutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func computeOpenLibraryUrl(paths ...string) (*url.URL, error) {
|
func computeOpenLibraryUrl(paths ...string) (*url.URL, error) {
|
||||||
baseUrl := "https://openlibrary.org"
|
baseUrl := "https://openlibrary.org"
|
||||||
u, err := url.Parse(baseUrl)
|
return callapiutils.ComputeUrl(baseUrl, paths...)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, p := range paths {
|
|
||||||
u = u.JoinPath(p)
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchAndParseResult[T any](u *url.URL, queryResult *T) error {
|
|
||||||
resp, err := doOpenLibraryQuery(u)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
|
||||||
err = decoder.Decode(queryResult)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func doOpenLibraryQuery(u *url.URL) (*http.Response, error) {
|
|
||||||
client := &http.Client{}
|
|
||||||
req, err := http.NewRequest("GET", u.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Add("Accept", "application/json")
|
|
||||||
req.Header.Add("User-Agent", "PersonalLibraryManager/0.1 (artlef@protonmail.com)")
|
|
||||||
return client.Do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addQueryParamInt(u *url.URL, paramName string, paramValue int) {
|
|
||||||
addQueryParam(u, paramName, strconv.Itoa(paramValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
func addQueryParam(u *url.URL, paramName string, paramValue string) {
|
|
||||||
q := u.Query()
|
|
||||||
q.Set(paramName, paramValue)
|
|
||||||
u.RawQuery = q.Encode()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package openlibrary
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.artlef.fr/PersonalLibraryManager/internal/callapiutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OpenLibraryAuthorResult struct {
|
type OpenLibraryAuthorResult struct {
|
||||||
@@ -46,7 +48,7 @@ func CallOpenLibraryAuthor(openLibraryId string) (OpenLibraryAuthorResult, error
|
|||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
var queryResult openLibraryParsedAuthor
|
var queryResult openLibraryParsedAuthor
|
||||||
err = fetchAndParseResult(u, &queryResult)
|
err = callapiutils.FetchAndParseResult(u, &queryResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.artlef.fr/PersonalLibraryManager/internal/callapiutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OpenLibraryBookResult struct {
|
type OpenLibraryBookResult struct {
|
||||||
@@ -63,7 +65,7 @@ func CallOpenLibraryBook(openLibraryId string) (OpenLibraryBookResult, error) {
|
|||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
var queryResult openLibraryParsedBook
|
var queryResult openLibraryParsedBook
|
||||||
err = fetchAndParseResult(u, &queryResult)
|
err = callapiutils.FetchAndParseResult(u, &queryResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package openlibrary
|
package openlibrary
|
||||||
|
|
||||||
|
import "git.artlef.fr/PersonalLibraryManager/internal/callapiutils"
|
||||||
|
|
||||||
type OpenLibrarySearchResult struct {
|
type OpenLibrarySearchResult struct {
|
||||||
Books []OpenLibrarySearchBook `json:"docs"`
|
Books []OpenLibrarySearchBook `json:"docs"`
|
||||||
NumFound int `json:"numFound"`
|
NumFound int `json:"numFound"`
|
||||||
@@ -18,12 +20,12 @@ func CallOpenLibrarySearch(searchterm string, limit int, offset int) (OpenLibrar
|
|||||||
return queryResult, err
|
return queryResult, err
|
||||||
}
|
}
|
||||||
if limit != 0 {
|
if limit != 0 {
|
||||||
addQueryParamInt(u, "limit", limit)
|
callapiutils.AddQueryParamInt(u, "limit", limit)
|
||||||
}
|
}
|
||||||
if offset != 0 {
|
if offset != 0 {
|
||||||
addQueryParamInt(u, "offset", offset)
|
callapiutils.AddQueryParamInt(u, "offset", offset)
|
||||||
}
|
}
|
||||||
addQueryParam(u, "q", searchterm)
|
callapiutils.AddQueryParam(u, "q", searchterm)
|
||||||
err = fetchAndParseResult(u, &queryResult)
|
err = callapiutils.FetchAndParseResult(u, &queryResult)
|
||||||
return queryResult, err
|
return queryResult, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type BookGet struct {
|
|||||||
Title string `json:"title" binding:"required,max=300"`
|
Title string `json:"title" binding:"required,max=300"`
|
||||||
Author string `json:"author" binding:"max=100"`
|
Author string `json:"author" binding:"max=100"`
|
||||||
ISBN string `json:"isbn"`
|
ISBN string `json:"isbn"`
|
||||||
|
InventaireId string `json:"inventaireid"`
|
||||||
OpenLibraryId string `json:"openlibraryid"`
|
OpenLibraryId string `json:"openlibraryid"`
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
Rating int `json:"rating"`
|
Rating int `json:"rating"`
|
||||||
@@ -23,7 +24,7 @@ type BookGet struct {
|
|||||||
func FetchBookGet(db *gorm.DB, userId uint, bookId uint64) (BookGet, error) {
|
func FetchBookGet(db *gorm.DB, userId uint, bookId uint64) (BookGet, error) {
|
||||||
var book BookGet
|
var book BookGet
|
||||||
query := db.Model(&model.Book{})
|
query := db.Model(&model.Book{})
|
||||||
selectQueryString := "books.title, authors.name as author, books.isbn, books.open_library_id, books.summary, " +
|
selectQueryString := "books.title, authors.name as author, books.isbn, books.inventaire_id, books.open_library_id, books.summary, " +
|
||||||
"user_books.rating, user_books.read, user_books.want_read, " +
|
"user_books.rating, user_books.read, user_books.want_read, " +
|
||||||
"DATE(user_books.start_read_date) as start_read_date, " +
|
"DATE(user_books.start_read_date) as start_read_date, " +
|
||||||
"DATE(user_books.end_read_date) AS end_read_date, " +
|
"DATE(user_books.end_read_date) AS end_read_date, " +
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type BookSearchGet struct {
|
|||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Title string `json:"title" binding:"required,max=300"`
|
Title string `json:"title" binding:"required,max=300"`
|
||||||
Author string `json:"author" binding:"max=100"`
|
Author string `json:"author" binding:"max=100"`
|
||||||
OpenLibraryId string `json:"openlibraryid"`
|
InventaireID string `json:"inventaireid"`
|
||||||
Rating int `json:"rating"`
|
Rating int `json:"rating"`
|
||||||
Read bool `json:"read"`
|
Read bool `json:"read"`
|
||||||
WantRead bool `json:"wantread"`
|
WantRead bool `json:"wantread"`
|
||||||
@@ -70,7 +70,7 @@ func fetchBookSearchQuery(db *gorm.DB, userId uint, searchterm string) *gorm.DB
|
|||||||
|
|
||||||
func fetchBookSearchQueryBuilder(db *gorm.DB, userId uint) *gorm.DB {
|
func fetchBookSearchQueryBuilder(db *gorm.DB, userId uint) *gorm.DB {
|
||||||
query := db.Model(&model.Book{})
|
query := db.Model(&model.Book{})
|
||||||
query = query.Select("books.id, books.title, authors.name as author, books.open_library_id, user_books.rating, user_books.read, user_books.want_read, " + selectStaticFilesPath())
|
query = query.Select("books.id, books.title, authors.name as author, books.inventaire_id, user_books.rating, user_books.read, user_books.want_read, " + selectStaticFilesPath())
|
||||||
query = joinAuthors(query)
|
query = joinAuthors(query)
|
||||||
query = query.Joins("left join user_books on (user_books.book_id = books.id and user_books.user_id = ?)", userId)
|
query = query.Joins("left join user_books on (user_books.book_id = books.id and user_books.user_id = ?)", userId)
|
||||||
query = joinStaticFiles(query)
|
query = joinStaticFiles(query)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
|
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
|
||||||
|
"git.artlef.fr/PersonalLibraryManager/internal/inventaire"
|
||||||
"git.artlef.fr/PersonalLibraryManager/internal/model"
|
"git.artlef.fr/PersonalLibraryManager/internal/model"
|
||||||
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
|
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
|
||||||
"git.artlef.fr/PersonalLibraryManager/internal/openlibrary"
|
"git.artlef.fr/PersonalLibraryManager/internal/openlibrary"
|
||||||
@@ -12,7 +13,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type bookPostImport struct {
|
type bookPostImport struct {
|
||||||
OpenLibraryId string `json:"openlibraryid" binding:"required,max=50"`
|
InventaireID string `json:"inventaireid" binding:"required,max=50"`
|
||||||
|
Lang string `json:"lang" binding:"required,max=5"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostImportBookHandler(ac appcontext.AppContext) {
|
func PostImportBookHandler(ac appcontext.AppContext) {
|
||||||
@@ -28,12 +30,12 @@ func PostImportBookHandler(ac appcontext.AppContext) {
|
|||||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
openLibraryBook, err := openlibrary.CallOpenLibraryBook(request.OpenLibraryId)
|
inventaireBook, err := inventaire.CallInventaireBook(request.InventaireID, request.Lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
book, err := saveOpenLibraryBookToDb(ac, request.OpenLibraryId, openLibraryBook, &user)
|
book, err := saveInventaireBookToDb(ac, inventaireBook, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||||
return
|
return
|
||||||
@@ -41,15 +43,15 @@ func PostImportBookHandler(ac appcontext.AppContext) {
|
|||||||
ac.C.JSON(200, gin.H{"id": book.ID})
|
ac.C.JSON(200, gin.H{"id": book.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveOpenLibraryBookToDb(ac appcontext.AppContext, openLibraryId string, openLibraryBook openlibrary.OpenLibraryBookResult, user *model.User) (*model.Book, error) {
|
func saveInventaireBookToDb(ac appcontext.AppContext, inventaireBook inventaire.InventaireBookResult, user *model.User) (*model.Book, error) {
|
||||||
author, err := fetchOrCreateOpenLibraryAuthor(ac, openLibraryBook.AuthorID)
|
author, err := fetchOrCreateInventaireAuthor(ac, inventaireBook.Author)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
book := model.Book{
|
book := model.Book{
|
||||||
Title: openLibraryBook.Title,
|
Title: inventaireBook.Title,
|
||||||
Summary: openLibraryBook.Description,
|
Summary: inventaireBook.Description,
|
||||||
OpenLibraryId: openLibraryId,
|
InventaireID: inventaireBook.ID,
|
||||||
Author: *author,
|
Author: *author,
|
||||||
AddedBy: *user,
|
AddedBy: *user,
|
||||||
}
|
}
|
||||||
@@ -57,17 +59,18 @@ func saveOpenLibraryBookToDb(ac appcontext.AppContext, openLibraryId string, ope
|
|||||||
return &book, err
|
return &book, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchOrCreateOpenLibraryAuthor(ac appcontext.AppContext, openlibraryAuthorId string) (*model.Author, error) {
|
func fetchOrCreateInventaireAuthor(ac appcontext.AppContext, inventaireAuthor *inventaire.InventaireAuthorResult) (*model.Author, error) {
|
||||||
var author model.Author
|
var author model.Author
|
||||||
res := ac.Db.Where("open_library_id = ?", openlibraryAuthorId).First(&author)
|
res := ac.Db.Where("inventaire_id = ?", inventaireAuthor.ID).First(&author)
|
||||||
err := res.Error
|
err := res.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
newAuthor, err := createAuthorFromOpenLibrary(ac, openlibraryAuthorId)
|
newAuthor := model.Author{
|
||||||
if err != nil {
|
Name: inventaireAuthor.Name,
|
||||||
return &author, err
|
Description: inventaireAuthor.Description,
|
||||||
|
InventaireID: inventaireAuthor.ID,
|
||||||
}
|
}
|
||||||
return newAuthor, nil
|
return &newAuthor, nil
|
||||||
} else {
|
} else {
|
||||||
return &author, err
|
return &author, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
|
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
|
||||||
|
"git.artlef.fr/PersonalLibraryManager/internal/inventaire"
|
||||||
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
|
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
|
||||||
"git.artlef.fr/PersonalLibraryManager/internal/openlibrary"
|
"git.artlef.fr/PersonalLibraryManager/internal/openlibrary"
|
||||||
"git.artlef.fr/PersonalLibraryManager/internal/query"
|
"git.artlef.fr/PersonalLibraryManager/internal/query"
|
||||||
@@ -38,24 +39,24 @@ func GetSearchBooksHandler(ac appcontext.AppContext) {
|
|||||||
if len(books) > 0 {
|
if len(books) > 0 {
|
||||||
returnedBooks = books
|
returnedBooks = books
|
||||||
} else {
|
} else {
|
||||||
queryResult, err := openlibrary.CallOpenLibrarySearch(searchterm, limit, offset)
|
queryResult, err := inventaire.CallInventaireSearch(searchterm, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
returnedBooks = OpenLibraryBooksToBookSearchGet(queryResult.Books)
|
returnedBooks = OpenLibraryBooksToBookSearchGet(queryResult.Results)
|
||||||
}
|
}
|
||||||
ac.C.JSON(http.StatusOK, returnedBooks)
|
ac.C.JSON(http.StatusOK, returnedBooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenLibraryBooksToBookSearchGet(OLbooks []openlibrary.OpenLibrarySearchBook) []query.BookSearchGet {
|
func OpenLibraryBooksToBookSearchGet(OLbooks []inventaire.InventaireSearchBook) []query.BookSearchGet {
|
||||||
var books []query.BookSearchGet
|
var books []query.BookSearchGet
|
||||||
for _, b := range OLbooks {
|
for _, b := range OLbooks {
|
||||||
bookSearchGet := query.BookSearchGet{
|
bookSearchGet := query.BookSearchGet{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Title: b.Title,
|
Title: b.Label,
|
||||||
Author: b.Authors[0],
|
Author: "",
|
||||||
OpenLibraryId: b.OpenLibraryId,
|
InventaireID: b.ID,
|
||||||
Rating: 0,
|
Rating: 0,
|
||||||
Read: false,
|
Read: false,
|
||||||
WantRead: false,
|
WantRead: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user