Added inventaire import from ISBN

This commit is contained in:
2026-02-02 20:31:57 +01:00
parent 21162cc63e
commit 82db737d30
7 changed files with 167 additions and 48 deletions

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref, computed } from 'vue'
import { putReadBook, getImagePathOrDefault } from './api.js'
import { putReadBook, getImagePathOrDefault, postImportBook } from './api.js'
import { useRouter } from 'vue-router'
const router = useRouter();
@@ -8,6 +8,7 @@
const props = defineProps({
id: Number,
inventaireid: String,
isinventaireedition: Boolean,
title: String,
author: String,
description: String,
@@ -19,20 +20,32 @@
const imagePathOrDefault = computed(() => getImagePathOrDefault(props.coverPath));
const error = ref(null)
async function onUserBookRead() {
const res = await putReadBook(props.id);
if (res.ok) {
router.push('/books')
} else {
res.json().then((json) => (error.value = json));
async function onUserBookRead() {
const res = await putReadBook(props.id);
if (res.ok) {
router.push('/books')
} else {
res.json().then((json) => (error.value = json));
}
}
}
function openBook() {
if (props.id != 0) {
router.push(`/book/${props.id}`);
async function openBook() {
if (props.id != 0) {
router.push(`/book/${props.id}`);
} else if (props.isinventaireedition) {
importInventaireEdition()
} else if (props.inventaireid != "") {
router.push(`/import/inventaire/${props.inventaireid}`)
}
}
async function importInventaireEdition(inventaireid) {
const res = await postImportBook(props.inventaireid, navigator.language.substring(0,2));
const json = await res.json();
if (res.ok) {
router.push(`/book/${json.id}`);
} else {
router.push(`/import/inventaire/${props.inventaireid}`)
error.value = json;
}
}

View File

@@ -9,25 +9,11 @@ import (
"net/url"
"testing"
"git.artlef.fr/PersonalLibraryManager/internal/dto"
"git.artlef.fr/PersonalLibraryManager/internal/testutils"
"github.com/stretchr/testify/assert"
)
type bookSearchGet struct {
Count int64 `json:"count"`
Books []bookSearchGetBook `json:"books"`
}
type bookSearchGetBook struct {
Id uint `json:"id"`
Title string `json:"title" binding:"required,max=300"`
Author string `json:"author" binding:"max=100"`
Rating int `json:"rating"`
Read bool `json:"read"`
WantRead bool `json:"wantread"`
CoverPath string `json:"coverPath"`
}
func TestSearchBook_MultipleBooks(t *testing.T) {
result := testSearchBook(t, "san", "", "")
@@ -39,10 +25,10 @@ func TestSearchBook_OneBookNotUserBook(t *testing.T) {
result := testSearchBook(t, "iliade", "", "")
assert.Equal(t, int64(1), result.Count)
assert.Equal(t,
[]bookSearchGetBook{{
[]dto.BookSearchGetBook{{
Title: "Iliade",
Author: "Homère",
Id: 29,
ID: 29,
Rating: 0,
Read: false,
WantRead: false,
@@ -55,10 +41,10 @@ func TestSearchBook_OneBookRead(t *testing.T) {
result := testSearchBook(t, "dieux", "", "")
assert.Equal(t, int64(1), result.Count)
assert.Equal(t,
[]bookSearchGetBook{{
[]dto.BookSearchGetBook{{
Title: "Les dieux ont soif",
Author: "Anatole France",
Id: 4,
ID: 4,
Rating: 7,
Read: true,
WantRead: false,
@@ -71,10 +57,10 @@ func TestSearchBook_ISBN(t *testing.T) {
result := testSearchBook(t, "9782070337903", "", "")
assert.Equal(t, int64(1), result.Count)
assert.Equal(t,
[]bookSearchGetBook{{
[]dto.BookSearchGetBook{{
Title: "Le complot contre l'Amérique",
Author: "Philip Roth",
Id: 22,
ID: 22,
Rating: 6,
Read: true,
WantRead: false,
@@ -83,6 +69,25 @@ func TestSearchBook_ISBN(t *testing.T) {
result.Books)
}
func TestSearchBook_ISBNInventaire(t *testing.T) {
result := testSearchBook(t, "9782253158400", "", "")
assert.Equal(t, int64(1), result.Count)
assert.Equal(t,
[]dto.BookSearchGetBook{{
ID: 0,
Title: "Les premières enquêtes de Maigret",
Author: "Georges Simenon",
Description: "roman de Georges Simenon",
InventaireID: "isbn:9782253158400",
IsInventaireEdition: true,
Rating: 0,
Read: false,
WantRead: false,
CoverPath: "https://inventaire.io/img/entities/17663a503d984204078c910d5bae68d52942b47d",
}},
result.Books)
}
func TestSearchBook_Limit(t *testing.T) {
result := testSearchBook(t, "a", "10", "")
assert.Equal(t, 10, len(result.Books))
@@ -94,7 +99,7 @@ func TestSearchBook_Offset(t *testing.T) {
assert.Equal(t, 3, len(result.Books))
}
func testSearchBook(t *testing.T, searchterm string, limit string, offset string) bookSearchGet {
func testSearchBook(t *testing.T, searchterm string, limit string, offset string) dto.BookSearchGet {
router := testutils.TestSetup()
u, err := url.Parse("/search/" + searchterm)
@@ -112,13 +117,17 @@ func testSearchBook(t *testing.T, searchterm string, limit string, offset string
u.RawQuery = q.Encode()
}
q := u.Query()
q.Set("lang", "fr")
u.RawQuery = q.Encode()
token := testutils.ConnectDemoUser(router)
req, _ := http.NewRequest("GET", u.String(), nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
var result bookSearchGet
var result dto.BookSearchGet
s := w.Body.String()
err = json.Unmarshal([]byte(s), &result)
if err != nil {

View File

@@ -37,13 +37,14 @@ type BookSearchGet struct {
}
type BookSearchGetBook struct {
ID uint `json:"id"`
Title string `json:"title" binding:"required,max=300"`
Author string `json:"author" binding:"max=100"`
Description string `json:"description"`
InventaireID string `json:"inventaireid"`
Rating int `json:"rating"`
Read bool `json:"read"`
WantRead bool `json:"wantread"`
CoverPath string `json:"coverPath"`
ID uint `json:"id"`
Title string `json:"title" binding:"required,max=300"`
Author string `json:"author" binding:"max=100"`
Description string `json:"description"`
InventaireID string `json:"inventaireid"`
IsInventaireEdition bool `json:"isinventaireedition"`
Rating int `json:"rating"`
Read bool `json:"read"`
WantRead bool `json:"wantread"`
CoverPath string `json:"coverPath"`
}

View File

@@ -125,3 +125,27 @@ func TestCallInventaireEdition(t *testing.T) {
},
result)
}
func TestCallInventaireEditionFromISBN(t *testing.T) {
result, err := CallInventaireFromISBN("9782070379248", "fr")
if err != nil {
t.Error(err)
}
assert.Equal(t,
&InventaireEditionDetailedSingleResult{
Id: "isbn:9782070379248",
Title: "Du côté de chez swann",
Author: &InventaireAuthorResult{
ID: "Q7199",
Name: "Marcel Proust",
Description: "écrivain, critique et essayiste français",
},
Description: "roman de Marcel Proust",
ISBN: "978-2-07-037924-8",
Publisher: "Éditions Gallimard",
ReleaseDate: "1988",
Image: "https://inventaire.io/img/entities/646a3ef031d6a1adc27cb5f556d2a403a2f525a7",
Lang: "fr",
},
result)
}

View File

@@ -16,6 +16,14 @@ type InventaireEditionDetailedSingleResult struct {
Lang string
}
type ErrorEditionNotFound struct {
InventaireId string
}
func (e *ErrorEditionNotFound) Error() string {
return fmt.Sprintf("No edition found on inventaire for id %s\n", e.InventaireId)
}
func CallInventaireEdition(inventaireId string, lang string) (InventaireEditionDetailedSingleResult, error) {
var result InventaireEditionDetailedSingleResult
editionQueryResults, err := callInventaireEditionEntities([]string{inventaireId})
@@ -24,7 +32,7 @@ func CallInventaireEdition(inventaireId string, lang string) (InventaireEditionD
}
var editionQueryResult inventaireEditionQueryEntity
if len(editionQueryResults.Entities) < 1 {
return result, fmt.Errorf("No edition found on inventaire for id %s", inventaireId)
return result, &ErrorEditionNotFound{InventaireId: inventaireId}
}
editionQueryResult = editionQueryResults.Entities[0]

View File

@@ -0,0 +1,16 @@
package inventaire
import "errors"
func CallInventaireFromISBN(isbn string, lang string) (*InventaireEditionDetailedSingleResult, error) {
inventaireInfo, err := CallInventaireEdition("isbn:"+isbn, lang)
if err != nil {
if errors.Is(err, &ErrorEditionNotFound{}) {
//suppress the not found error, returns nil instead
return nil, nil
} else {
return &inventaireInfo, err
}
}
return &inventaireInfo, err
}

View File

@@ -2,6 +2,7 @@ package routes
import (
"net/http"
"regexp"
"strings"
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
@@ -51,17 +52,64 @@ func GetSearchBooksHandler(ac appcontext.AppContext) {
returnedBooks = dto.BookSearchGet{Count: count, Inventaire: false, Books: books}
}
if params.Inventaire || len(returnedBooks.Books) == 0 {
queryResult, err := inventaire.CallInventaireSearch(searchterm, params.Lang, limit, offset)
returnedBooksPtr, err := searchInInventaireAPI(searchterm, limit, offset, params)
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
returnedBooks = InventaireBooksToBookSearchGet(queryResult)
returnedBooks = *returnedBooksPtr
}
ac.C.JSON(http.StatusOK, returnedBooks)
}
func InventaireBooksToBookSearchGet(results inventaire.InventaireSearchResult) dto.BookSearchGet {
func searchInInventaireAPI(searchterm string, limit int, offset int, params dto.BookSearchGetParam) (*dto.BookSearchGet, error) {
isIsbn, err := regexp.Match(`\d{10,13}`, []byte(searchterm))
if err != nil {
return nil, err
}
if isIsbn {
queryResult, err := inventaire.CallInventaireFromISBN(searchterm, params.Lang)
if err != nil {
return nil, err
}
var bookSearchGet dto.BookSearchGet
if queryResult != nil {
bookSearchGet = inventaireEditionToBookSearchGet(*queryResult)
} else {
bookSearchGet = dto.BookSearchGet{Count: 0, Inventaire: true}
}
return &bookSearchGet, err
} else {
queryResult, err := inventaire.CallInventaireSearch(searchterm, params.Lang, limit, offset)
if err != nil {
return nil, err
}
bookSearchGet := inventaireBooksToBookSearchGet(queryResult)
return &bookSearchGet, err
}
}
func inventaireEditionToBookSearchGet(result inventaire.InventaireEditionDetailedSingleResult) dto.BookSearchGet {
var books []dto.BookSearchGetBook
bookSearchGetBook := dto.BookSearchGetBook{
ID: 0,
Title: result.Title,
Author: result.Author.Name,
Description: result.Description,
InventaireID: result.Id,
IsInventaireEdition: true,
Rating: 0,
Read: false,
WantRead: false,
CoverPath: result.Image,
}
books = append(books, bookSearchGetBook)
return dto.BookSearchGet{Count: 1, Inventaire: true, Books: books}
}
func inventaireBooksToBookSearchGet(results inventaire.InventaireSearchResult) dto.BookSearchGet {
var books []dto.BookSearchGetBook
for _, b := range results.Results {
coverPath := ""