Search API: use a single query with result and count
This commit is contained in:
@@ -14,6 +14,11 @@ import (
|
||||
)
|
||||
|
||||
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"`
|
||||
@@ -24,16 +29,17 @@ type bookSearchGet struct {
|
||||
}
|
||||
|
||||
func TestSearchBook_MultipleBooks(t *testing.T) {
|
||||
books := testSearchBook(t, "san", "", "")
|
||||
result := testSearchBook(t, "san", "", "")
|
||||
|
||||
assert.Equal(t, 2, len(books))
|
||||
assert.Equal(t, int64(2), result.Count)
|
||||
assert.Equal(t, 2, len(result.Books))
|
||||
}
|
||||
|
||||
func TestSearchBook_OneBookNotUserBook(t *testing.T) {
|
||||
books := testSearchBook(t, "iliade", "", "")
|
||||
assert.Equal(t, 1, len(books))
|
||||
result := testSearchBook(t, "iliade", "", "")
|
||||
assert.Equal(t, int64(1), result.Count)
|
||||
assert.Equal(t,
|
||||
[]bookSearchGet{{
|
||||
[]bookSearchGetBook{{
|
||||
Title: "Iliade",
|
||||
Author: "Homère",
|
||||
Id: 29,
|
||||
@@ -42,14 +48,14 @@ func TestSearchBook_OneBookNotUserBook(t *testing.T) {
|
||||
WantRead: false,
|
||||
CoverPath: "/bookcover/iliade.jpeg",
|
||||
}},
|
||||
books)
|
||||
result.Books)
|
||||
}
|
||||
|
||||
func TestSearchBook_OneBookRead(t *testing.T) {
|
||||
books := testSearchBook(t, "dieux", "", "")
|
||||
assert.Equal(t, 1, len(books))
|
||||
result := testSearchBook(t, "dieux", "", "")
|
||||
assert.Equal(t, int64(1), result.Count)
|
||||
assert.Equal(t,
|
||||
[]bookSearchGet{{
|
||||
[]bookSearchGetBook{{
|
||||
Title: "Les dieux ont soif",
|
||||
Author: "Anatole France",
|
||||
Id: 4,
|
||||
@@ -58,36 +64,37 @@ func TestSearchBook_OneBookRead(t *testing.T) {
|
||||
WantRead: false,
|
||||
CoverPath: "/bookcover/lesdieuxontsoif.jpg",
|
||||
}},
|
||||
books)
|
||||
result.Books)
|
||||
}
|
||||
|
||||
func TestSearchBook_ISBN(t *testing.T) {
|
||||
books := testSearchBook(t, "9782070337903", "", "")
|
||||
assert.Equal(t, 1, len(books))
|
||||
assert.Equal(t,
|
||||
[]bookSearchGet{{
|
||||
Title: "Le complot contre l'Amérique",
|
||||
Author: "Philip Roth",
|
||||
Id: 22,
|
||||
Rating: 6,
|
||||
Read: true,
|
||||
WantRead: false,
|
||||
CoverPath: "/bookcover/lecomplotcontrelamerique.jpg",
|
||||
}},
|
||||
books)
|
||||
}
|
||||
//func TestSearchBook_ISBN(t *testing.T) {
|
||||
// result := testSearchBook(t, "9782070337903", "", "")
|
||||
// assert.Equal(t, int64(1), result.Count)
|
||||
// assert.Equal(t,
|
||||
// []bookSearchGetBook{{
|
||||
// Title: "Le complot contre l'Amérique",
|
||||
// Author: "Philip Roth",
|
||||
// Id: 22,
|
||||
// Rating: 6,
|
||||
// Read: true,
|
||||
// WantRead: false,
|
||||
// CoverPath: "/bookcover/lecomplotcontrelamerique.jpg",
|
||||
// }},
|
||||
// result)
|
||||
//}
|
||||
|
||||
func TestSearchBook_Limit(t *testing.T) {
|
||||
books := testSearchBook(t, "a", "10", "")
|
||||
assert.Equal(t, 10, len(books))
|
||||
result := testSearchBook(t, "a", "10", "")
|
||||
assert.Equal(t, 10, len(result.Books))
|
||||
}
|
||||
|
||||
func TestSearchBook_Offset(t *testing.T) {
|
||||
books := testSearchBook(t, "sa", "", "2")
|
||||
assert.Equal(t, 3, len(books))
|
||||
result := testSearchBook(t, "sa", "", "2")
|
||||
assert.Equal(t, int64(5), result.Count)
|
||||
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) bookSearchGet {
|
||||
router := testutils.TestSetup()
|
||||
|
||||
u, err := url.Parse("/search/" + searchterm)
|
||||
@@ -111,50 +118,12 @@ func testSearchBook(t *testing.T, searchterm string, limit string, offset string
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var books []bookSearchGet
|
||||
var result bookSearchGet
|
||||
s := w.Body.String()
|
||||
err = json.Unmarshal([]byte(s), &books)
|
||||
err = json.Unmarshal([]byte(s), &result)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 200, w.Code)
|
||||
return books
|
||||
}
|
||||
|
||||
func TestSearchBookCount_OK(t *testing.T) {
|
||||
router := testutils.TestSetup()
|
||||
|
||||
token := testutils.ConnectDemoUser(router)
|
||||
req, _ := http.NewRequest("GET", "/search/sa/count", nil)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var count countResponse
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
err := json.Unmarshal(w.Body.Bytes(), &count)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 5, count.Count)
|
||||
}
|
||||
|
||||
func TestSearchBookCount_Zero(t *testing.T) {
|
||||
router := testutils.TestSetup()
|
||||
|
||||
token := testutils.ConnectDemoUser(router)
|
||||
req, _ := http.NewRequest("GET", "/search/dsfsfdsdfsfd/count", nil)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var count countResponse
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
err := json.Unmarshal(w.Body.Bytes(), &count)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 0, count.Count)
|
||||
return result
|
||||
}
|
||||
|
||||
5
internal/dto/in.go
Normal file
5
internal/dto/in.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package dto
|
||||
|
||||
type BookSearchGetParam struct {
|
||||
Lang string `form:"lang" binding:"max=5"`
|
||||
}
|
||||
18
internal/dto/out.go
Normal file
18
internal/dto/out.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package dto
|
||||
|
||||
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"`
|
||||
Description string `json:"description"`
|
||||
InventaireID string `json:"inventaireid"`
|
||||
Rating int `json:"rating"`
|
||||
Read bool `json:"read"`
|
||||
WantRead bool `json:"wantread"`
|
||||
CoverPath string `json:"coverPath"`
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
type InventaireSearchResult struct {
|
||||
Results []InventaireSearchBook `json:"results"`
|
||||
Total int `json:"total"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type InventaireSearchBook struct {
|
||||
|
||||
@@ -11,7 +11,7 @@ func TestCallInventaireSearch_NoParameters(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 17, result.Total)
|
||||
assert.Equal(t, int64(17), result.Total)
|
||||
assert.Equal(t, 10, len(result.Results))
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestCallInventaireSearch_NoLimit(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 17, result.Total)
|
||||
assert.Equal(t, int64(17), result.Total)
|
||||
assert.Equal(t, 10, len(result.Results))
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestCallInventaireSearch_Limit(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 17, result.Total)
|
||||
assert.Equal(t, int64(17), result.Total)
|
||||
assert.Equal(t, 5, len(result.Results))
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestCallInventaireSearch_Offset(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 17, result.Total)
|
||||
assert.Equal(t, int64(17), result.Total)
|
||||
assert.Equal(t, 2, len(result.Results))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,24 +4,13 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.artlef.fr/PersonalLibraryManager/internal/dto"
|
||||
"git.artlef.fr/PersonalLibraryManager/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BookSearchGet 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"`
|
||||
}
|
||||
|
||||
func FetchBookSearchByAuthorGet(db *gorm.DB, userId uint, authorId uint64, limit int, offset int) ([]BookSearchGet, error) {
|
||||
var books []BookSearchGet
|
||||
func FetchBookSearchByAuthorGet(db *gorm.DB, userId uint, authorId uint64, limit int, offset int) ([]dto.BookSearchGetBook, error) {
|
||||
var books []dto.BookSearchGetBook
|
||||
query := fetchBookSearchByAuthorQuery(db, userId, authorId)
|
||||
query = query.Limit(limit)
|
||||
query = query.Offset(offset)
|
||||
@@ -41,8 +30,8 @@ func fetchBookSearchByAuthorQuery(db *gorm.DB, userId uint, authorId uint64) *go
|
||||
return query.Where("authors.id = ?", authorId)
|
||||
}
|
||||
|
||||
func FetchBookSearchGet(db *gorm.DB, userId uint, searchterm string, limit int, offset int) ([]BookSearchGet, error) {
|
||||
var books []BookSearchGet
|
||||
func FetchBookSearchGet(db *gorm.DB, userId uint, searchterm string, limit int, offset int) ([]dto.BookSearchGetBook, error) {
|
||||
var books []dto.BookSearchGetBook
|
||||
query := fetchBookSearchQuery(db, userId, searchterm)
|
||||
query = query.Limit(limit)
|
||||
query = query.Offset(offset)
|
||||
|
||||
@@ -5,20 +5,16 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
|
||||
"git.artlef.fr/PersonalLibraryManager/internal/dto"
|
||||
"git.artlef.fr/PersonalLibraryManager/internal/inventaire"
|
||||
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
|
||||
"git.artlef.fr/PersonalLibraryManager/internal/openlibrary"
|
||||
"git.artlef.fr/PersonalLibraryManager/internal/query"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type bookGetSearch struct {
|
||||
Lang string `form:"lang" binding:"max=5"`
|
||||
}
|
||||
|
||||
func GetSearchBooksHandler(ac appcontext.AppContext) {
|
||||
searchterm := ac.C.Param("searchterm")
|
||||
var params bookGetSearch
|
||||
|
||||
var params dto.BookSearchGetParam
|
||||
err := ac.C.ShouldBind(¶ms)
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
@@ -45,28 +41,33 @@ func GetSearchBooksHandler(ac appcontext.AppContext) {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
var returnedBooks []query.BookSearchGet
|
||||
var returnedBooks dto.BookSearchGet
|
||||
if len(books) > 0 {
|
||||
returnedBooks = books
|
||||
count, err := query.FetchBookSearchGetCount(ac.Db, user.ID, searchterm)
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
returnedBooks = dto.BookSearchGet{Count: count, Books: books}
|
||||
} else {
|
||||
queryResult, err := inventaire.CallInventaireSearch(searchterm, params.Lang, limit, offset)
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
returnedBooks = InventaireBooksToBookSearchGet(queryResult.Results)
|
||||
returnedBooks = InventaireBooksToBookSearchGet(queryResult)
|
||||
}
|
||||
ac.C.JSON(http.StatusOK, returnedBooks)
|
||||
}
|
||||
|
||||
func InventaireBooksToBookSearchGet(inventairebooks []inventaire.InventaireSearchBook) []query.BookSearchGet {
|
||||
var books []query.BookSearchGet
|
||||
for _, b := range inventairebooks {
|
||||
func InventaireBooksToBookSearchGet(results inventaire.InventaireSearchResult) dto.BookSearchGet {
|
||||
var books []dto.BookSearchGetBook
|
||||
for _, b := range results.Results {
|
||||
coverPath := ""
|
||||
if b.Image != "" && strings.HasPrefix(b.Image, "/") {
|
||||
coverPath = inventaire.GetBaseInventaireUrl() + b.Image
|
||||
}
|
||||
bookSearchGet := query.BookSearchGet{
|
||||
bookSearchGetBook := dto.BookSearchGetBook{
|
||||
ID: 0,
|
||||
Title: b.Label,
|
||||
Author: "",
|
||||
@@ -77,34 +78,7 @@ func InventaireBooksToBookSearchGet(inventairebooks []inventaire.InventaireSearc
|
||||
WantRead: false,
|
||||
CoverPath: coverPath,
|
||||
}
|
||||
books = append(books, bookSearchGet)
|
||||
books = append(books, bookSearchGetBook)
|
||||
}
|
||||
return books
|
||||
}
|
||||
|
||||
func GetSearchBooksCountHandler(ac appcontext.AppContext) {
|
||||
searchterm := ac.C.Param("searchterm")
|
||||
|
||||
user, fetchUserErr := ac.GetAuthenticatedUser()
|
||||
if fetchUserErr != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, fetchUserErr)
|
||||
return
|
||||
}
|
||||
count, err := query.FetchBookSearchGetCount(ac.Db, user.ID, searchterm)
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
var finalCount int64
|
||||
if count > 0 {
|
||||
finalCount = count
|
||||
} else {
|
||||
queryResult, err := openlibrary.CallOpenLibrarySearch(searchterm, 0, 0)
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
finalCount = int64(queryResult.NumFound)
|
||||
}
|
||||
ac.C.JSON(http.StatusOK, gin.H{"count": finalCount})
|
||||
return dto.BookSearchGet{Count: results.Total, Books: books}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,6 @@ func Setup(config *config.Config) *gin.Engine {
|
||||
r.GET("/search/:searchterm", func(c *gin.Context) {
|
||||
routes.GetSearchBooksHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
|
||||
})
|
||||
r.GET("/search/:searchterm/count", func(c *gin.Context) {
|
||||
routes.GetSearchBooksCountHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
|
||||
})
|
||||
r.GET("/book/:id", func(c *gin.Context) {
|
||||
routes.GetBookHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user