author is now on separate table

This commit is contained in:
2025-11-24 23:55:41 +01:00
parent fa49f276fe
commit 3cbe9f909e
9 changed files with 166 additions and 54 deletions

View File

@@ -11,11 +11,20 @@ import (
"github.com/stretchr/testify/assert"
)
func TestPostBookHandler_Ok(t *testing.T) {
func TestPostBookHandler_OkAuthorExist(t *testing.T) {
bookJson :=
`{
"title": "Le château",
"author": "Kafka"
"title": "Le Procès",
"author": "Franz Kafka"
}`
testPostBookHandler(t, bookJson, 200)
}
func TestPostBookHandler_OkNewAuthor(t *testing.T) {
bookJson :=
`{
"title": "Le joueur d'échecs",
"author": "Stefan Zweig"
}`
testPostBookHandler(t, bookJson, 200)
}
@@ -23,7 +32,7 @@ func TestPostBookHandler_Ok(t *testing.T) {
func TestPostBookHandler_OkOnlyTitle(t *testing.T) {
bookJson :=
`{
"title": "Le château"
"title": "Le Procès"
}`
testPostBookHandler(t, bookJson, 200)
}

View File

@@ -61,6 +61,22 @@ func TestSearchBook_OneBookRead(t *testing.T) {
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_Limit(t *testing.T) {
books := testSearchBook(t, "a", "10", "")
assert.Equal(t, 10, len(books))

9
internal/model/author.go Normal file
View File

@@ -0,0 +1,9 @@
package model
import "gorm.io/gorm"
type Author struct {
gorm.Model
Name string
Description string
}

View File

@@ -6,8 +6,9 @@ type Book struct {
gorm.Model
Title string `json:"title" gorm:"not null"`
ISBN string `json:"isbn"`
Author string `json:"author"`
Summary string `json:"summary"`
Author Author
AuthorID uint
AddedBy User
AddedByID uint
Cover StaticFile

View File

@@ -22,14 +22,15 @@ type BookGet struct {
func FetchBookGet(db *gorm.DB, userId uint, bookId uint64) (BookGet, error) {
var book BookGet
query := db.Model(&model.Book{})
selectQueryString := "books.title, books.author, books.isbn, books.summary, " +
selectQueryString := "books.title, authors.name as author, books.isbn, books.summary, " +
"user_books.rating, user_books.read, user_books.want_read, " +
"DATE(user_books.start_read_date) as start_read_date, " +
"DATE(user_books.end_read_date) AS end_read_date, " +
selectStaticFilesPath()
query = query.Select(selectQueryString)
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 static_files on (static_files.id = books.cover_id)")
query = joinStaticFiles(query)
query = query.Where("books.id = ?", bookId)
res := query.First(&book)
return book, res.Error
@@ -113,9 +114,10 @@ func fetchWantReadUserBookQuery(db *gorm.DB, userId uint) *gorm.DB {
func fetchUserBookGet(db *gorm.DB, userId uint) *gorm.DB {
query := db.Model(&model.UserBook{})
query = query.Select("books.id, books.title, books.author, user_books.rating, user_books.read, user_books.want_read, " + selectStaticFilesPath())
query = query.Select("books.id, books.title, authors.name as author, user_books.rating, user_books.read, user_books.want_read, " + selectStaticFilesPath())
query = query.Joins("left join books on (books.id = user_books.book_id)")
query = query.Joins("left join static_files on (static_files.id = books.cover_id)")
query = joinAuthors(query)
query = joinStaticFiles(query)
query = query.Where("user_id = ?", userId)
return query
}
@@ -123,3 +125,11 @@ func fetchUserBookGet(db *gorm.DB, userId uint) *gorm.DB {
func selectStaticFilesPath() string {
return "(CASE COALESCE(static_files.path, '') WHEN '' THEN '' ELSE concat('" + fileutils.GetWsLinkPrefix() + "', static_files.path) END) as CoverPath"
}
func joinAuthors(query *gorm.DB) *gorm.DB {
return query.Joins("left join authors on (authors.id = books.author_id)")
}
func joinStaticFiles(query *gorm.DB) *gorm.DB {
return query.Joins("left join static_files on (static_files.id = books.cover_id)")
}

View File

@@ -1,6 +1,7 @@
package query
import (
"regexp"
"strings"
"git.artlef.fr/PersonalLibraryManager/internal/model"
@@ -34,10 +35,22 @@ func FetchBookSearchGetCount(db *gorm.DB, userId uint, searchterm string) (int64
}
func fetchBookSearchQuery(db *gorm.DB, userId uint, searchterm string) *gorm.DB {
query := db.Model(&model.Book{})
query = query.Select("books.id, books.title, books.author, user_books.rating, user_books.read, user_books.want_read, " + selectStaticFilesPath())
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 static_files on (static_files.id = books.cover_id)")
query = query.Where("LOWER(books.title) LIKE ?", "%"+strings.ToLower(searchterm)+"%")
query := fetchBookSearchQueryBuilder(db, userId)
isIsbn, _ := regexp.Match(`\d{10,13}`, []byte(searchterm))
if isIsbn {
query = query.Where("books.isbn = ?", searchterm)
} else {
query = query.Where("LOWER(books.title) LIKE ?", "%"+strings.ToLower(searchterm)+"%")
}
return query
}
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 = 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)
return query
}

View File

@@ -1,9 +1,12 @@
package routes
import (
"errors"
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
"git.artlef.fr/PersonalLibraryManager/internal/model"
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
"gorm.io/gorm"
)
type bookPostCreate struct {
@@ -29,8 +32,8 @@ func PostBookHandler(ac appcontext.AppContext) {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
bookDb := bookWsToDb(book, &user)
err = ac.Db.Model(&model.Book{}).Save(&bookDb).Error
err = saveBookToDb(ac, book, &user)
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
@@ -38,14 +41,38 @@ func PostBookHandler(ac appcontext.AppContext) {
ac.C.String(200, "Success")
}
func bookWsToDb(b bookPostCreate, user *model.User) model.Book {
func saveBookToDb(ac appcontext.AppContext, b bookPostCreate, user *model.User) error {
author, err := fetchOrCreateAuthor(ac, b.Author)
if err != nil {
return err
}
book := model.Book{
Title: b.Title,
Author: b.Author,
AddedBy: *user,
Title: b.Title,
AuthorID: author.ID,
AddedBy: *user,
}
if b.CoverID > 0 {
book.CoverID = b.CoverID
}
return book
return ac.Db.Save(&book).Error
}
func fetchOrCreateAuthor(ac appcontext.AppContext, name string) (*model.Author, error) {
var author model.Author
res := ac.Db.Where("name = ?", name)
err := res.Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
author = model.Author{Name: name}
err = ac.Db.Save(&author).Error
if err != nil {
return &author, err
}
return &author, nil
} else {
return &author, err
}
} else {
return &author, nil
}
}

View File

@@ -28,6 +28,7 @@ func PutReadUserBookHandler(ac appcontext.AppContext) {
}
userbook, err := fetchOrCreateUserBook(ac, bookId, &user)
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
@@ -71,6 +72,7 @@ func PutWantReadUserBookHandler(ac appcontext.AppContext) {
}
userbook, err := fetchOrCreateUserBook(ac, bookId, &user)
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
userbook.WantRead = wantread.WantRead
@@ -124,6 +126,7 @@ func PutRateUserBookHandler(ac appcontext.AppContext) {
}
userbook, err := fetchOrCreateUserBook(ac, bookId, &user)
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
userbook.Rating = rating.Rating
@@ -200,12 +203,10 @@ func fetchOrCreateUserBook(ac appcontext.AppContext, bookId uint, user *model.Us
userbook = createUserBook(bookId, user)
err = ac.Db.Save(&userbook).Error
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return userbook, err
}
return userbook, nil
} else {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return userbook, err
}
} else {