From 167e7116499a9caa11c608e45fc1f7541966ea32 Mon Sep 17 00:00:00 2001 From: Arthur Lefebvre Date: Sat, 27 Dec 2025 01:23:40 +0100 Subject: [PATCH] Add open library API call when search has no result --- front/src/BookListElement.vue | 7 +- internal/model/book.go | 19 +- .../openlibrary/example_openlibrary_call.json | 1096 +++++++++++++++++ internal/openlibrary/openlibrary.go | 70 ++ internal/openlibrary/openlibrary_test.go | 27 + internal/query/search.go | 17 +- internal/routes/booksearchget.go | 45 +- 7 files changed, 1261 insertions(+), 20 deletions(-) create mode 100644 internal/openlibrary/example_openlibrary_call.json create mode 100644 internal/openlibrary/openlibrary.go create mode 100644 internal/openlibrary/openlibrary_test.go diff --git a/front/src/BookListElement.vue b/front/src/BookListElement.vue index b2cad99..f853e8e 100644 --- a/front/src/BookListElement.vue +++ b/front/src/BookListElement.vue @@ -7,6 +7,7 @@ const props = defineProps({ id: Number, + openlibraryid: String, title: String, author: String, rating: Number, @@ -27,7 +28,11 @@ async function onUserBookRead() { } function openBook() { - router.push(`/book/${props.id}`) + if (props.id != 0) { + router.push(`/book/${props.id}`); + } else { + console.log("Open Library Id : " + props.openlibraryid); + } } diff --git a/internal/model/book.go b/internal/model/book.go index 14a1b53..5a7635c 100644 --- a/internal/model/book.go +++ b/internal/model/book.go @@ -4,13 +4,14 @@ import "gorm.io/gorm" type Book struct { gorm.Model - Title string `json:"title" gorm:"not null"` - ISBN string `json:"isbn"` - Summary string `json:"summary"` - Author Author - AuthorID uint - AddedBy User - AddedByID uint - Cover StaticFile - CoverID uint + Title string `json:"title" gorm:"not null"` + ISBN string `json:"isbn"` + OpenLibraryId string `json:"openlibraryid"` + Summary string `json:"summary"` + Author Author + AuthorID uint + AddedBy User + AddedByID uint + Cover StaticFile + CoverID uint } diff --git a/internal/openlibrary/example_openlibrary_call.json b/internal/openlibrary/example_openlibrary_call.json new file mode 100644 index 0000000..25e2af8 --- /dev/null +++ b/internal/openlibrary/example_openlibrary_call.json @@ -0,0 +1,1096 @@ +{ + "numFound": 25, + "start": 0, + "numFoundExact": true, + "num_found": 25, + "documentation_url": "https://openlibrary.org/dev/docs/api/search", + "q": "man in high castle", + "offset": null, + "docs": [ + { + "author_key": [ + "OL274606A", + "OL3121003A" + ], + "author_name": [ + "Philip K. Dick", + "Michelle Charrier" + ], + "cover_edition_key": "OL1566828M", + "cover_i": 420452, + "ebook_access": "printdisabled", + "edition_count": 83, + "first_publish_year": 1962, + "has_fulltext": true, + "ia": [ + "lematreduhautcht0000phil", + "maninhighcastle0000dick", + "maninhighcastle0000dick_n3g2", + "lemaitreduhautch0000dick", + "isbn_9782277125679", + "maninhighcastle00phil", + "manhighcastlevin00dick" + ], + "ia_collection_s": "americana;americanuniversity-ol;binghamton-ol;delawarecountydistrictlibrary;delawarecountydistrictlibrary-ol;gwulibraries-ol;hamiltonpubliclibrary-ol;internetarchivebooks;johnshopkins-ol;library_of_atlantis;miltonpubliclibrary-ol;openlibrary-d-ol;printdisabled;riceuniversity-ol;uni-ol;wilsoncollege-ol;worthingtonlibraries-ol", + "key": "/works/OL2172403W", + "language": [ + "ger", + "pol", + "spa", + "jpn", + "eng", + "por", + "ita", + "gre", + "chi", + "fre", + "hrv" + ], + "public_scan_b": false, + "title": "The Man in the High Castle" + }, + { + "author_key": [ + "OL9388A" + ], + "author_name": [ + "William Shakespeare" + ], + "cover_edition_key": "OL26587990M", + "cover_i": 8281954, + "ebook_access": "public", + "edition_count": 2120, + "first_publish_year": 1603, + "has_fulltext": true, + "ia": [ + "bib_fict_4117276", + "bib_fict_4117279", + "bib_fict_9868854", + "hamlenesikhdenma00shakuoft", + "amletoprincipedi00shak", + "tragedyofhamlet01shak", + "tragshakespeares00shakrich", + "shake00spearestragshakrich", + "tragicallhistori00shakuoft", + "hamletasarranged00shakuoft", + "shakespearesham00shak", + "hamletardenshake0000will", + "hamlettragedyinf00shak", + "hamletprinceofde00shakuoft", + "tragicallhistori00shak_0", + "hamlet31797gut", + "hamletdramaemcin25667gut", + "tragedyofhamletp00shak_6", + "hamlet01787gut", + "shakespearestrag00shakiala", + "hamletprinceofde01122gut", + "hamlet01524gut", + "hamletprinzvondn07276gut", + "hamlet02265gut", + "hamlet15632gut", + "hamlet27761gut", + "hamlet15032gut", + "hamletprinceofde00shak_3", + "cu31924013137314", + "tragedyofhamletp00shak", + "shakespearestrag00shak", + "tragedieofhamlet00shakuoft", + "hamletfirstquart00shakuoft", + "hamletparallelte00shakuoft", + "hamlet00shak_6", + "shakespearestrag0000shak", + "uvrescompletes01shak", + "cu31924013134451", + "selectplaysedite02shakuoft", + "firsteditionoftr00shakuoft", + "cu31924020325969", + "firsteditionoftr00shak_0", + "hamlet00shak", + "firsteditionoftr00shak", + "tragedyofhamletp02shak", + "hamlet01shak", + "hamletprinceofde00shak", + "shakespearestrag00shakesp", + "hamletprincedenm00shakuoft", + "hamlet_1209_librivox", + "hamlet2_1206_librivox", + "hamlet_0911_librivox", + "hamletauthoritat00shak_2", + "hamlet3426shak", + "newvariorumediti12shak", + "newvariorumediti11shak", + "hamletprincipede500shak", + "hmltshak00shakuoft", + "shakespearestrag1906shak", + "bub_gb_Tj0OAAAAIAAJ", + "bub_gb_fEEOAAAAIAAJ", + "oldunclenedsongs00philrich", + "hamlettragedy00shak", + "hamlettragedyinf02shak", + "shakespearestrag1890shak", + "tragedieofhamlet00shak", + "shakespearestrag251shak", + "cu31924013137256", + "hamletprinceofde00shak_4", + "hamlet1368shak", + "hamletshak", + "hamletasperforme00shak", + "tragedyofham00shak", + "hamletdramaencin1798shak", + "cihm_990486", + "tragedieofhamlet00shak_1", + "ForrestHamlet", + "shakespearestrag00shak_0", + "tragicallhistori00shak", + "bub_gb_-IafnF65cNYC", + "hamletprinceofde00shak_2", + "hamletprinceofde00shak_1", + "tragedyofhamletp00shak_3", + "tragedyofhamletp00shak_2", + "tragedyofhamletp00shak_4", + "tragedyofhamletp00shak_7", + "tragedyofhamletp00shak_5", + "tragedyofhamletp00shak_0", + "tragedyofhamletp00shak_1", + "shaham", + "thetragicallhist09077gut", + "HamletArmenian", + "shakespearestrag05shakesp", + "cu31924014138733", + "hamlettragedyinf00shakrich", + "shakespeareshaml01shak", + "tragedyofhamletp01shak", + "earestragshakesp00shakrich", + "A079238161", + "tragedyofhamlet00shakuoft", + "hamletby00shakuoft", + "cihm_27989", + "yaleshakespeare07shakiala", + "tragedyofhamletp00shakrich", + "tragedieofhamle00shak", + "dererstedeutsche00shak", + "tragedyofhamletp1900shak", + "tragedyofhamlet1900shak", + "hamlettragdiee00shak", + "hamlettragedyinf01shak", + "hamletprinceofde01shak", + "hamlet02shak", + "tragedyofhamle00shakuoft", + "hamletparallelte00shak", + "hamlettragedy01shak", + "hamletprinzvondn01schr", + "prinzhamletvondn01schi", + "hamlet_175", + "uvrescompltesd1859shak", + "hamletprincipede3261shak", + "Hamleto", + "shakespeare-hamlet-first-quarto-1603", + "HHAZ2801", + "tragedyhamletpr00chamgoog", + "shakespearesele01shakgoog", + "tragedyhamletpr02shakgoog", + "tragedyhamlet00blacgoog", + "shakespearestra68shakgoog", + "hamlet02unkngoog", + "hamletatragedyi00shakgoog", + "shakespearestra70shakgoog", + "tragedyhamletpr00murrgoog", + "hamletvolume00shakgoog", + "tragedyhamletpr01hartgoog", + "hamlet02shakgoog", + "playsshakespear00hudsgoog", + "hamlet04shakgoog", + "shakespearesham01shakgoog", + "hamlet08shakgoog", + "firsteditiontra00shakgoog", + "hamlet01unkngoog", + "tragedyhamletpr04shakgoog", + "tragedyhamletpr00hartgoog", + "tragedyhamletpr01shakgoog", + "shakespearestra49shakgoog", + "shakespearestra00gollgoog", + "shakespearestra02gollgoog", + "shakespearestra16shakgoog", + "hamlet03shakgoog", + "hamlet00unkngoog", + "shakespearesham00shakgoog", + "hamlet06shakgoog", + "hamletatragedy00sothgoog", + "shakespearestra30shakgoog", + "shakespearestra03gollgoog", + "shakespearestra29rolfgoog", + "hamletkrlewiczd00shakgoog", + "shakespearestra09rolfgoog", + "shakespearestra05shakgoog", + "hamlet07shakgoog", + "hamletprinceden01shakgoog", + "shakspereshamle00furngoog", + "hamlettragediae00lisbgoog", + "shakespearestra15rolfgoog", + "shakespearestra67shakgoog", + "hamlet05shakgoog", + "tragedyhamletpr03shakgoog", + "hamletprinceden00shakgoog", + "hamletprinzvond00shakgoog", + "tragicallhistor00stragoog", + "shakespereshaml00shakgoog", + "shakspeareshaml00heusgoog", + "hamletprinceden00clargoog", + "facsimilecopies00hallgoog", + "hamlet00timmgoog", + "hamlettraverspi00klingoog", + "shakespearestra14shakgoog", + "tragedyhamlet01dowdgoog", + "firsteditiontra01shakgoog", + "hamletatragedy01sothgoog", + "shakespearestra00hudsgoog", + "shakespearestra08gollgoog", + "shakespearestra01hudsgoog", + "shakespearestra05gollgoog", + "hamletprinceden00wriggoog", + "tragedyhamlet00shakgoog", + "tragediehamletp00macdgoog", + "hamletbywilliam00shakgoog", + "williamshakespea0000thom_q3r5", + "hamlet0000gorm", + "hamlet0000shak_s5l5", + "hamlet0000shak_u1r3", + "hamlet0000shak_p3n2", + "hamletshakespear0000will", + "hamlet0000shak_k8o9", + "tragedyofhamletp0000shak_p2o5", + "hamlet0000shak_r7u5", + "hamletshakespear0000shak", + "amleto0000shak", + "hamlet0000shak_q3b7", + "hamlettexteinteg0000shak", + "hamlet0000shak_r8v4", + "hamlettragediete0000shak", + "williamshakespea0000shak_f4v4", + "hamlet0000shak_o1t4", + "tragedyofhamletp0000shak_p7u8", + "hamlet0000shak_v0b7", + "hamlet0000garf_f4t0", + "hamlet0000shak_v1r4", + "hamletprinceofde0000mcev", + "hamletpreswicked0000will", + "hamletprinceofde0000davi", + "tragedyofhamlet100shak", + "tragedyofhamletp0000shak_j0y3", + "hamletprinceofde00shak_0", + "hamletshak02shak", + "hamlet0000shak_r6o2", + "bwb_KR-686-143", + "hamlet0000shak_j3w6", + "hamlet300shak", + "hamletcommentary0000shak", + "tragedyofhamlet10000shak", + "tragedyofhamlet0000shak", + "hamlet0000will_q5j0", + "tragedyofhamlet0000will", + "hamlet0000will_r8j4", + "hamletprinceofde0000unse", + "hamletsh00shak", + "hamlet0000shak_s2e3", + "haml00shak", + "hamlet0000shak_l0e1", + "tragedyofhamletp0000shak_p2o5", + "tragedyofhamletp00sha_sq8", + "hamletcompleteau0000shak", + "tragedyofhamletc0000shak", + "hamlet0000shak", + "hamlet0000shak_l2i8", + "hamletseries2320000unse", + "hamlet0000shak_w4e9", + "tragedyofhamlet00shak", + "hamuleitehamlet0000shak", + "hamlet0000shak_d4g2", + "shakespeareplays0000shak", + "tragedyofhamletp0000shak_e8x3", + "hamulaite0000shak", + "hamletprinceofd000shak", + "hamlettheatre0000unse", + "yaleshakespeare0000unse_q4c7", + "tragedyofhamlet300shak", + "hamletsh00shak", + "hamlet0000shak_v9h8", + "hamletsha00shak", + "hamletfolgershak00will", + "tragicalhistoryo00shak", + "hamletnewfolgerl00will", + "hamlethamlet00will", + "hamlet00shak_3", + "hamletcharnwoodc00will", + "hamlet400shak", + "hamletauthoritat00shak_1", + "hamletfurn00shak", + "tragedyofhamletp1963shak", + "isbn_9780671463960", + "tragedyofhamletp1958shak", + "tragedyofhamlet200shak", + "hamlet04thshak", + "hamletthefolgerl0000unse", + "hamlet0000shak_t1b2", + "hamletteachersgu0000shak", + "hamlet0000shak_a4v8", + "hamlet0000will_f8q3", + "hamletprinceofde0000shak_x2y1", + "hamleteditedrend0000shak", + "hamletprinceofde0000shak", + "tragedyofhamle00shak", + "hamlet0000shak_v6t4", + "threetexthamletp0000shak", + "hamlet0000shak_l0t2", + "tragedyofhamletp0000shak_i7b7", + "tragedyofhamletp0000shak_l3n8", + "hamletauthoritat0000shak", + "isbn_0671489488", + "hamletfirstquart0000shak", + "hamlet0000shak_y3q6", + "tragedyofhamletp0000shak_u0a0", + "hamletprinceofde1985shak", + "hamlet0000shak_i2j3", + "hamletprestwickh00shak", + "hamlet0000shak_h6d0", + "hamlet00will_1", + "tragedyofhamletp1957shak", + "hamletauthoritat00shak", + "hamlet0000shak_e3x2", + "isbn_0192824856", + "hamlet0000rice", + "hamlet00will", + "hamlet0000shak_q2t9", + "tragedyofhamlet1939pshak", + "hamlet0000will", + "hamletcompletest0000shak", + "hamulaite0000shak_c3r6", + "hamlet0000shak_w9y3", + "hamletprinceofde0000shak_k7b3", + "hamlet0000shak_v7o2", + "hamlet0000shak_f4d2", + "hamlet0000shak_b2v1", + "hamlet0000shak_p2w1", + "tragicalhistoryo0000shak_d1b4", + "hamlet0000shak_i4r8", + "tragedyofhamletp0000shak_f3q2", + "lesdeuxhamlet0000shak", + "hamlet0000shak_s1x1", + "hamlet0000shak_v5b6", + "bwb_W8-ACQ-358", + "tradegyofhamletp0000will", + "tragedyofhamletp0000gbha", + "hamlet0000shak_n7k7", + "shakespeareshaml0000shak", + "hamletshortersha0000unse", + "hamletannotatedb0000shak", + "hamlet0000shak_m0g4", + "hamlet0000shak_i6u8", + "hamlet0000shak_c3i7", + "klingonhamletres00shak", + "tragedyofhamle00sha", + "tragedyofhamletp0000shak_e6a4", + "firstquartoofham0000shak", + "hamlet0000will_u9e4", + "hamlet0000will_y9h2", + "bwb_S0-CCJ-437", + "hamletriversidel0000will", + "hamletsignetclas00will", + "tragedyofhamletpshak00shak", + "hamlet00shak_4", + "hamlet0000shak_e2q5", + "hamlet200shak", + "tragedyofhamletp0000shak", + "hamlet0000will_t5b0", + "bwb_P6-BQV-975", + "tragedyofhamletp0000shak_m4w6", + "hamlet0bantam0shak", + "hamletauthoritat00shak_0", + "hamlet2001shak", + "hamletshak00shak", + "isbn_9780582097209", + "tragedyofhamlet400shak", + "isbn_0393095916", + "shakespeareshaml00shak", + "hamlettextsof1600000shak", + "hamlet0000shak_z0z5", + "hamlet0000shak_t9d5", + "hamlet0000shak_b9c2", + "hamletnewversion0000shak", + "hamlet0000shak_v6j2", + "tragedieofhamlet00shak_0", + "hamlet100shak", + "hamlet0000shak_h2s8", + "hamletwordsworth00will_0", + "hamlet00will_0", + "williamshakespea0000shak_e0n8", + "hamlet0000shak_z5m4", + "hamlet0000shak_b4x9", + "shakespeareondou00shak_098", + "hamlettheannotat00shak" + ], + "ia_collection_s": "Johns_Hopkins_University;additional_collections;americana;americanuniversity-ol;audio_bookspoetry;barryuniversity-ol;belmont-ol;bibliotecauniversitariadesevilla;bibliotheca_fictiva;binghamton-ol;bncfirenze;booksbylanguage;booksbylanguage_esperanto;bostonpubliclibrary;bplsceep;bplsctpbs;brigham_young_university;brighamyounguniversityidaho;cdl;cnusd-ol;community;cornell;cua-ol;dartmouthlibrary-ol;delawarecountydistrictlibrary;delawarecountydistrictlibrary-ol;denverpubliclibrary-ol;drakeuniversity-ol;duke_libraries;duljantz;europeanlibraries;family_history_library;folkscanomy;folkscanomy_unsorted;framingham-ol;getty;greatbooks;gutenberg;gwulibraries-ol;hamiltonpubliclibrary-ol;inlibrary;internetarchivebooks;ithacacollege-ol;johnshopkins-ol;kalamazoocollege-ol;kellylibrary;library_of_atlantis;library_of_congress;librivoxaudio;lincolncollection;marygrovecollege;marymount-ol;microfilm;miltonpubliclibrary-ol;occidentalcollegelibrary-ol;openlibrary-d-ol;opensource;phillipsacademy-preview;popularchinesebooks;printdisabled;prscr;randolph-macon-college-ol;riceuniversity-ol;robarts;robert-bedrosian-library;rochester-ol;schroederlibrary-ol;spandr;spokanepubliclibrary-ol;stmaryscountylibrary;the-claremont-colleges-ol;toronto;trent_university;tulsacc-ol;ualberta_historical_school;ualbertawiedrick;uclalibrarypreservation;uhmauicollege-ol;unb-ol;unclibraries;uni-ol;university_of_alberta_libraries;university_of_alberta_libraries_microfilm;university_of_california_libraries;university_of_illinois_urbana-champaign;university_of_toronto;universityofarizona-ol;universityofcoloradoboulder-ol;universityofoklahoma-ol;universityofottawa;universityofthewest-ol;uslprototype;wilsoncollege-ol;worksofshakespeare;worthingtonlibraries-ol", + "key": "/works/OL9170454W", + "language": [ + "lit", + "gre", + "hun", + "sin", + "bul", + "ina", + "fre", + "tlh", + "rus", + "eng", + "por", + "rum", + "tib", + "fin", + "pol", + "dan", + "baq", + "per", + "heb", + "amh", + "cze", + "urd", + "guj", + "tgl", + "swe", + "arm", + "dut", + "kur", + "cat", + "alb", + "ara", + "epo", + "srp", + "fao", + "chi", + "spa", + "tur", + "san", + "wel", + "jpn", + "und", + "ita", + "slo", + "ice", + "ukr", + "mlt", + "ger", + "mul" + ], + "lending_edition_s": "OL58595448M", + "lending_identifier_s": "bib_fict_4117276", + "public_scan_b": true, + "title": "Hamlet", + "id_standard_ebooks": [ + "william-shakespeare/hamlet" + ], + "id_project_gutenberg": [ + "31797", + "25667", + "1787", + "1122", + "1524", + "7276", + "2265", + "15632", + "27761", + "15032", + "9077" + ], + "id_librivox": [ + "6828", + "6718", + "346" + ] + }, + { + "author_key": [ + "OL8354734A" + ], + "author_name": [ + "SuperSummary" + ], + "cover_edition_key": "OL35659077M", + "cover_i": 12366237, + "ebook_access": "no_ebook", + "edition_count": 618, + "first_publish_year": 2017, + "has_fulltext": false, + "key": "/works/OL21868175W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Study Guide" + }, + { + "author_key": [ + "OL25342A" + ], + "author_name": [ + "Mary Shelley" + ], + "cover_edition_key": "OL35649409M", + "cover_i": 12356249, + "ebook_access": "public", + "edition_count": 2186, + "first_publish_year": 1818, + "has_fulltext": true, + "ia": [ + "frankensteinormo00shel_8", + "cu31924105428902", + "ghostseer01schiuoft", + "frankensteinormo00shel_9", + "frankensteinorm00shelgoog", + "frankensteinorm02shelgoog", + "frankensteinoelm0000shel", + "annotatedfranken00shel", + "frankenstein0000gran_r1i1", + "frankensteinormo00newy", + "essentialfranken0000shel", + "frankensteinbyma0000leon", + "frankensteinspan00mary", + "frankensteinormo0000shel_g7b4", + "frankenstein00mary", + "frankensteinormo00shel", + "frankensteinormo00shel_2", + "frankensteinormo0000shel_w2m7", + "frankensteinormo0000shel_p7p9", + "frankensteinormo0000shel_j7h8", + "frankensteinormo0000shel_f6i0", + "frankensteinoelm0000shel_z3p1", + "frankensteinoule0000shel", + "frankensteinoule0000mary", + "isbn_9783612271594_0", + "frankensteinoule0000shel_f1c3", + "frankensteinoule0000shel_z2r3", + "frankenstein0000sibl", + "frankensteinoule0000shel_g8i0", + "frankensteinormo0000shel_n0x1", + "frankensteinormo0000shel_d0c9", + "frankensteinormo0000shel", + "frankensteinormo00shel_1", + "frankensteinormo0000shel_b4k1", + "frankenstein0000carl", + "frankenstein0000gran", + "frankensteinormo0000mary_p0h5", + "frankensteinormo0000unse", + "frankensteinnote0000boyd", + "robertandrewpark0000shel", + "frankensteinormo00mary", + "frankensteinormo0000shel_i4s8", + "frankensteinormo0000shel_r3f7", + "frankensteinormo0000shel_o4k0", + "frankensteinormo0000mary_c2t3", + "frankenstein0000shel_c7g0", + "frankensteinwith0000shel_l6r1", + "frankenstein0000shel_u8j4", + "frankenstein0000shel", + "frankenstein0000burg", + "frankenstein0000shel_f6n0", + "frankenstein00mary_1", + "frankensteinchil00mary", + "frankensteiniout0000shel", + "frankensteincomp0000shel_w9l1", + "frankensteinormo0000shel_b2j8", + "frankenstein0000shel_a2z7", + "frankenstein00shel_1", + "frankensteinbarn00mary", + "frankensteinormo0000shel_w5i2", + "frankensteinmode00shel", + "frankenstein00shel_2", + "frankenstein00", + "frankenstein0000shel_y1i4", + "frankensteinjetd00mary", + "frankensteinormo0000shel_a7g4", + "frankenstein0000shel_q9r9", + "frankenstein0000shel_a0d1", + "frankenstein0000shel_b1p6", + "frankenstein0000shel_c7c0", + "frankensteinormo0000shel_z5s0", + "frankenstein0000tavn", + "frankensteinorm000shel", + "frankenstein00shel_3", + "frankenstein00mary_0", + "frankenstein0000shel_u1h8", + "frankensteincomp00shel", + "frankensteinormo0000shel_m4e7", + "frankenstein00shel", + "frankenstein0000shel_x5t7", + "frankensteinormo0000shel_d5q4", + "frankenstein00shel_0", + "frankensteinbarn00mary_0", + "frankenstein0000shel_n4y6", + "frankensteinkapl00shel", + "frankenstein0000shel_e5x7", + "frankensteinormo00shel_0", + "frankensteinlynd00shel", + "aleph0000coel_t8v8", + "frankensteinormo0000shel_n5l3", + "frankenstein0000shel_l7b9", + "frankenstein0000shel_p1t5", + "frankenstein0000shel_s4u2", + "frankenstein0000shel_h4s7", + "frankensteingrea00mary" + ], + "ia_collection_s": "additional_collections;americana;americanuniversity-ol;barryuniversity-ol;binghamton-ol;bpljordan-ol;cnusd-ol;cornell;dartmouthlibrary-ol;delawarecountydistrictlibrary;delawarecountydistrictlibrary-ol;fedlink;framingham-ol;gwulibraries-ol;hamiltonpubliclibrary-ol;inlibrary;internetarchivebooks;johnshopkins-ol;library_of_atlantis;library_of_congress;marygrovecollege;marymount-ol;miltonpubliclibrary-ol;occidentalcollegelibrary-ol;openlibrary-d-ol;printdisabled;riceuniversity-ol;robarts;rochester-ol;sciencefiction;stmaryscountylibrary;the-claremont-colleges-ol;toronto;trent_university;tulsacc-ol;udc-ol;unb-ol;uni-ol;university_of_toronto;universityofarizona-ol;universityofcoloradoboulder-ol;universityofoklahoma-ol;universityofthewest-ol;wilsoncollege-ol;worthingtonlibraries-ol", + "key": "/works/OL450063W", + "language": [ + "dut", + "ita", + "fre", + "rus", + "eng", + "por", + "ger", + "chi", + "spa" + ], + "lending_edition_s": "OL26683337M", + "lending_identifier_s": "frankensteinormo00shel_8", + "public_scan_b": true, + "title": "Frankenstein or The Modern Prometheus", + "id_standard_ebooks": [ + "mary-shelley/frankenstein", + "mary-shelley/frankenstein" + ] + }, + { + "author_key": [ + "OL18236A", + "OL6006699A" + ], + "author_name": [ + "Alexandre Dumas", + "Auguste Maquet" + ], + "cover_edition_key": "OL45889099M", + "cover_i": 14564690, + "ebook_access": "public", + "edition_count": 113, + "first_publish_year": 1848, + "has_fulltext": true, + "ia": [ + "oeuvrescomplte02duma", + "dergrafvonbrage00dumagoog", + "levicomtedebrage0000duma_l9b9" + ], + "ia_collection_s": "americana;inlibrary;internetarchivebooks;printdisabled;toronto;universityofottawa", + "key": "/works/OL36738W", + "language": [ + "und", + "fre", + "ger", + "spa", + "eng", + "rus", + "vie" + ], + "lending_edition_s": "OL24758461M", + "lending_identifier_s": "oeuvrescomplte02duma", + "public_scan_b": true, + "title": "Le vicomte de Bragelonne, ou, dix ans plus tard" + }, + { + "author_key": [ + "OL14417966A" + ], + "author_name": [ + "Book*Sense" + ], + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 2015, + "has_fulltext": false, + "key": "/works/OL41060346W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Man in the High Castle" + }, + { + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 1997, + "has_fulltext": false, + "key": "/works/OL8151595M", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "The Man in the High Castle" + }, + { + "author_key": [ + "OL18236A" + ], + "author_name": [ + "Alexandre Dumas" + ], + "cover_edition_key": "OL16777528M", + "cover_i": 14565211, + "ebook_access": "public", + "edition_count": 132, + "first_publish_year": 1857, + "has_fulltext": true, + "ia": [ + "vicomtedebragelo0000alex", + "worksofdumas14dumaiala", + "vicomte_de_bragelonne_1110_librivox", + "dartagnan_romances3_pt_1_ten_years_later_1907_librivox", + "vicomtedebragelo0000unse_t1t8", + "vicomtedebragelo00duma", + "romancesalexand52dumagoog", + "vicomtedebragelo0000alex_m1l0", + "vicomtedebragelo00alex", + "worksofalexandre0000unse_x4u1", + "vicomtedebragelo0000alex_t7o7" + ], + "ia_collection_s": "americana;audio_bookspoetry;cdl;inlibrary;internetarchivebooks;librivoxaudio;printdisabled;prscr;unclibraries", + "key": "/works/OL259573W", + "language": [ + "eng", + "fre" + ], + "lending_edition_s": "OL16777528M", + "lending_identifier_s": "vicomtedebragelo0000alex", + "public_scan_b": true, + "title": "The Vicomte of Bragelonne", + "id_librivox": [ + "the-vicomte-de-bragelonne-by-alexandre-dumas", + "the-dartagnan-romances-vol-3-part-1-the-vicomte-de-bragelonne-ten-years-later-by-alexandre-dumas" + ] + }, + { + "author_key": [ + "OL7546483A" + ], + "author_name": [ + "Char story" + ], + "ebook_access": "no_ebook", + "edition_count": 1305, + "first_publish_year": 2018, + "has_fulltext": false, + "key": "/works/OL40214694W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Notebook" + }, + { + "author_key": [ + "OL201163A" + ], + "author_name": [ + "Paul Sammon" + ], + "cover_edition_key": "OL28921810M", + "cover_i": 12727353, + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 2019, + "has_fulltext": false, + "key": "/works/OL21352611W", + "language": [ + "eng" + ], + "public_scan_b": false, + "subtitle": "Creating the Alt World", + "title": "The Man in the High Castle" + }, + { + "author_key": [ + "OL274606A" + ], + "author_name": [ + "Philip K. Dick" + ], + "cover_edition_key": "OL35412196M", + "cover_i": 12254230, + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 2020, + "has_fulltext": false, + "key": "/works/OL26253283W", + "language": [ + "tur" + ], + "public_scan_b": false, + "title": "Yüksek Şato’daki Adam" + }, + { + "author_key": [ + "OL274606A" + ], + "author_name": [ + "Philip K. Dick" + ], + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 2012, + "has_fulltext": false, + "key": "/works/OL32222889W", + "language": [ + "heb" + ], + "public_scan_b": false, + "title": "ha-Ish ba-metsudah ha-ramah" + }, + { + "author_key": [ + "OL11261892A" + ], + "author_name": [ + "Catherine Fricke" + ], + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 2020, + "has_fulltext": false, + "key": "/works/OL30768535W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Man in the High Castle Dots Lines Spirals Coloring Book" + }, + { + "author_key": [ + "OL27270A" + ], + "author_name": [ + "Paul Levinson" + ], + "ebook_access": "no_ebook", + "edition_count": 2, + "first_publish_year": 2021, + "has_fulltext": false, + "key": "/works/OL25957051W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Paul Levinson Talks to Rufus Sewell about the Man in the High Castle" + }, + { + "author_key": [ + "OL14417966A" + ], + "author_name": [ + "Book*Sense" + ], + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 2015, + "has_fulltext": false, + "key": "/works/OL41060234W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Man in the High Castle Philip K. Dick a Summary, Analysis and Review" + }, + { + "author_key": [ + "OL8318970A" + ], + "author_name": [ + "Bruce Krajewski" + ], + "cover_edition_key": "OL26823778M", + "cover_i": 8492466, + "ebook_access": "no_ebook", + "edition_count": 2, + "first_publish_year": 2017, + "has_fulltext": false, + "key": "/works/OL19368514W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "The Man in the High Castle and Philosophy: Subversive Reports from Another Reality (Popular Culture and Philosophy Book 111)" + }, + { + "author_key": [ + "OL736277A" + ], + "author_name": [ + "Paul Hoffman" + ], + "ebook_access": "no_ebook", + "edition_count": 2, + "first_publish_year": 2021, + "has_fulltext": false, + "key": "/works/OL28354538W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "White Devil" + }, + { + "author_key": [ + "OL11439354A" + ], + "author_name": [ + "SpeedyReads" + ], + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 2018, + "has_fulltext": false, + "key": "/works/OL40254727W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Summary of the Man in the High Castle by Philip K. Dick - Finish Entire Novel in 15 Minutes" + }, + { + "author_key": [ + "OL4723243A" + ], + "author_name": [ + "Sylvain Neuvel" + ], + "cover_edition_key": "OL29891324M", + "cover_i": 10643647, + "ebook_access": "no_ebook", + "edition_count": 6, + "first_publish_year": 2021, + "has_fulltext": false, + "key": "/works/OL21915306W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "History of What Comes Next" + }, + { + "author_key": [ + "OL4771068A" + ], + "author_name": [ + "John Davis, Servant to Mistris Lewis" + ], + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 1641, + "has_fulltext": false, + "key": "/works/OL11357854W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "A great discovery of a damnable plot at Rugland castle in Monmoth-shire in Wales" + }, + { + "author_key": [ + "OL2744889A" + ], + "author_name": [ + "Jacob Boehme" + ], + "cover_edition_key": "OL28382972M", + "cover_i": 11041701, + "ebook_access": "public", + "edition_count": 1, + "first_publish_year": 1909, + "has_fulltext": true, + "ia": [ + "highdeepsearchin0000bhme" + ], + "ia_collection_s": "claremont_school_of_theology;internetarchivebooks", + "key": "/works/OL20950064W", + "language": [ + "eng" + ], + "lending_edition_s": "OL28382972M", + "lending_identifier_s": "highdeepsearchin0000bhme", + "public_scan_b": true, + "title": "The high and deep searching out of the threefold life of man through the three principles" + }, + { + "author_key": [ + "OL35377A" + ], + "author_name": [ + "John Newman" + ], + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 1619, + "has_fulltext": false, + "key": "/works/OL18857753M", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "A looking-glasse for petitioners" + }, + { + "author_key": [ + "OL1865923A" + ], + "author_name": [ + "William Lusk Crawford" + ], + "cover_edition_key": "OL4748460M", + "cover_i": 6372726, + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 1978, + "has_fulltext": false, + "key": "/works/OL6816323W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Ancestors & friends" + }, + { + "author_key": [ + "OL5292385A" + ], + "author_name": [ + "Paul Felgenhauer" + ], + "ebook_access": "no_ebook", + "edition_count": 1, + "first_publish_year": 1654, + "has_fulltext": false, + "key": "/works/OL32420113W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "Postilion, or, A new almanacke and astrologicke, prophetical, prognostication" + }, + { + "author_key": [ + "OL1387305A" + ], + "author_name": [ + "Kris Rampersad" + ], + "cover_edition_key": "OL25424169M", + "cover_i": 7253085, + "ebook_access": "no_ebook", + "edition_count": 2, + "first_publish_year": 2012, + "has_fulltext": false, + "key": "/works/OL16801744W", + "language": [ + "eng" + ], + "public_scan_b": false, + "title": "LiTTscapes - Landscapes of Fiction from Trinidad and Tobago" + } + ] +} diff --git a/internal/openlibrary/openlibrary.go b/internal/openlibrary/openlibrary.go new file mode 100644 index 0000000..17cdca7 --- /dev/null +++ b/internal/openlibrary/openlibrary.go @@ -0,0 +1,70 @@ +package openlibrary + +import ( + "encoding/json" + "io" + "net/http" + "net/url" + "strconv" + "strings" +) + +type OpenLibraryQueryResult struct { + Books []OpenLibraryBook `json:"docs"` + NumFound int `json:"numFound"` +} + +type OpenLibraryBook struct { + Title string `json:"title"` + Authors []string `json:"author_name"` + OpenLibraryId string `json:"cover_edition_key"` +} + +func CallOpenLibrary(searchterm string, limit int, offset int) (OpenLibraryQueryResult, error) { + var queryResult OpenLibraryQueryResult + baseUrl := "https://openlibrary.org" + booksApiUrl := "search.json" + u, err := url.Parse(baseUrl) + if err != nil { + return queryResult, err + } + u = u.JoinPath(booksApiUrl) + if limit != 0 { + addQueryParamInt(u, "limit", limit) + } + if offset != 0 { + addQueryParamInt(u, "offset", offset) + } + addQueryParam(u, "q", searchterm) + client := &http.Client{} + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return queryResult, err + } + req.Header.Add("Accept", "application/json") + req.Header.Add("User-Agent", "PersonalLibraryManager/0.1 (artlef@protonmail.com)") + resp, err := client.Do(req) + if err != nil { + return queryResult, err + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return queryResult, err + } + bodyString := string(bodyBytes) + decoder := json.NewDecoder(strings.NewReader(bodyString)) + err = decoder.Decode(&queryResult) + return queryResult, err +} + +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() +} diff --git a/internal/openlibrary/openlibrary_test.go b/internal/openlibrary/openlibrary_test.go new file mode 100644 index 0000000..1424fdd --- /dev/null +++ b/internal/openlibrary/openlibrary_test.go @@ -0,0 +1,27 @@ +package openlibrary_test + +import ( + "testing" + + "git.artlef.fr/PersonalLibraryManager/internal/openlibrary" + "github.com/stretchr/testify/assert" +) + +func TestCallOpenLibrary(t *testing.T) { + result, err := openlibrary.CallOpenLibrary("man in high castle", 0, 0) + assert.Nil(t, err) + assert.Equal(t, result.NumFound, 25) + assert.Equal(t, len(result.Books), 25) +} + +func TestCallOpenLibraryLimit(t *testing.T) { + result, err := openlibrary.CallOpenLibrary("man in high castle", 10, 0) + assert.Nil(t, err) + assert.Equal(t, len(result.Books), 10) +} + +func TestCallOpenLibraryOffset(t *testing.T) { + result, err := openlibrary.CallOpenLibrary("man in high castle", 0, 10) + assert.Nil(t, err) + assert.Equal(t, len(result.Books), 15) +} diff --git a/internal/query/search.go b/internal/query/search.go index 1393b7f..a42da84 100644 --- a/internal/query/search.go +++ b/internal/query/search.go @@ -9,13 +9,14 @@ import ( ) type BookSearchGet 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"` + ID uint `json:"id"` + Title string `json:"title" binding:"required,max=300"` + Author string `json:"author" binding:"max=100"` + OpenLibraryId string `json:"openlibraryid"` + Rating int `json:"rating"` + Read bool `json:"read"` + WantRead bool `json:"wantread"` + CoverPath string `json:"coverPath"` } func FetchBookSearchByAuthorGet(db *gorm.DB, userId uint, authorId uint64, limit int, offset int) ([]BookSearchGet, error) { @@ -69,7 +70,7 @@ func fetchBookSearchQuery(db *gorm.DB, userId uint, searchterm string) *gorm.DB func fetchBookSearchQueryBuilder(db *gorm.DB, userId uint) *gorm.DB { query := db.Model(&model.Book{}) - query = query.Select("books.id, books.title, authors.name as author, user_books.rating, user_books.read, user_books.want_read, " + selectStaticFilesPath()) + 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 = joinAuthors(query) query = query.Joins("left join user_books on (user_books.book_id = books.id and user_books.user_id = ?)", userId) query = joinStaticFiles(query) diff --git a/internal/routes/booksearchget.go b/internal/routes/booksearchget.go index 5532f5c..3e3d96e 100644 --- a/internal/routes/booksearchget.go +++ b/internal/routes/booksearchget.go @@ -5,6 +5,7 @@ import ( "git.artlef.fr/PersonalLibraryManager/internal/appcontext" "git.artlef.fr/PersonalLibraryManager/internal/myvalidator" + "git.artlef.fr/PersonalLibraryManager/internal/openlibrary" "git.artlef.fr/PersonalLibraryManager/internal/query" "github.com/gin-gonic/gin" ) @@ -33,7 +34,36 @@ func GetSearchBooksHandler(ac appcontext.AppContext) { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } - ac.C.JSON(http.StatusOK, books) + var returnedBooks []query.BookSearchGet + if len(books) > 0 { + returnedBooks = books + } else { + queryResult, err := openlibrary.CallOpenLibrary(searchterm, limit, offset) + if err != nil { + myvalidator.ReturnErrorsAsJsonResponse(&ac, err) + return + } + returnedBooks = OpenLibraryBooksToBookSearchGet(queryResult.Books) + } + ac.C.JSON(http.StatusOK, returnedBooks) +} + +func OpenLibraryBooksToBookSearchGet(OLbooks []openlibrary.OpenLibraryBook) []query.BookSearchGet { + var books []query.BookSearchGet + for _, b := range OLbooks { + bookSearchGet := query.BookSearchGet{ + ID: 0, + Title: b.Title, + Author: b.Authors[0], + OpenLibraryId: b.OpenLibraryId, + Rating: 0, + Read: false, + WantRead: false, + CoverPath: "", + } + books = append(books, bookSearchGet) + } + return books } func GetSearchBooksCountHandler(ac appcontext.AppContext) { @@ -49,5 +79,16 @@ func GetSearchBooksCountHandler(ac appcontext.AppContext) { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } - ac.C.JSON(http.StatusOK, gin.H{"count": count}) + var finalCount int64 + if count > 0 { + finalCount = count + } else { + queryResult, err := openlibrary.CallOpenLibrary(searchterm, 0, 0) + if err != nil { + myvalidator.ReturnErrorsAsJsonResponse(&ac, err) + return + } + finalCount = int64(queryResult.NumFound) + } + ac.C.JSON(http.StatusOK, gin.H{"count": finalCount}) }