add filter read/want read when browsing books

This commit is contained in:
2025-11-07 16:09:54 +01:00
parent 844ddaa7dc
commit f8ad54392a
11 changed files with 171 additions and 53 deletions

View File

@@ -1,14 +1,51 @@
<script setup>
import { ref } from 'vue'
import BookCard from './BookCard.vue';
import { getMyBooks } from './api.js'
const { data, error } = getMyBooks();
const FilterStates = Object.freeze({
READ: "read",
WANTREAD: "wantread",
});
let currentFilterState = ref(FilterStates.READ);
let { data, error } = getMyBooks(currentFilterState.value);
function fetchData() {
let res = getMyBooks(currentFilterState.value);
data = res.data;
error = res.error;
}
function onFilterButtonClick(newstate) {
currentFilterState.value = newstate;
fetchData();
}
function computeDynamicClass(state) {
return currentFilterState.value === state ? 'is-active is-primary' : '';
}
</script>
<template>
<div class="mb-5">
<button class="button is-medium"
@click="onFilterButtonClick(FilterStates.READ)"
:class="computeDynamicClass(FilterStates.READ)">
{{$t('bookbrowser.read')}}
</button>
<button class="button is-medium"
@click="onFilterButtonClick(FilterStates.WANTREAD)"
:class="computeDynamicClass(FilterStates.WANTREAD)">
{{$t('bookbrowser.wantread')}}
</button>
</div>
<div class="books">
<div v-if="error">{{$t('bookbrowser.error', {error: error.message})}}</div>
<div class="book" v-else-if="data" v-for="book in data" :key="book.id">
<BookCard v-bind="book" />

View File

@@ -29,8 +29,8 @@ function useFetch(url) {
return { data, error }
}
export function getMyBooks() {
return useFetch(baseUrl + '/mybooks');
export function getMyBooks(arg) {
return useFetch(baseUrl + '/mybooks/' + arg);
}
export function getSearchBooks(searchterm) {

View File

@@ -28,7 +28,9 @@
},
"bookbrowser": {
"error": "Error when loading books: {error}",
"loading": "Loading..."
"loading": "Loading...",
"read": "Read",
"wantread": "To read"
},
"searchbook": {
"error": "Error when loading books: {error}",

View File

@@ -28,7 +28,9 @@
},
"bookbrowser": {
"error": "Erreur pendant le chargement des livres: {error}",
"loading": "Chargement..."
"loading": "Chargement...",
"read": "Lu",
"wantread": "À lire"
},
"searchbook": {
"error": "Erreur pendant le chargement des livres: {error}",

View File

@@ -0,0 +1,50 @@
package apitest
import (
"testing"
"git.artlef.fr/PersonalLibraryManager/internal/testutils"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestGetReadBooksHandler_Demo(t *testing.T) {
router := testutils.TestSetup()
token := testutils.ConnectDemoUser(router)
books := testGetReadBooksHandler(t, router, token, 200)
assert.Equal(t, 23, len(books))
}
func TestGetReadBooksHandler_Demo2(t *testing.T) {
router := testutils.TestSetup()
token := testutils.ConnectDemo2User(router)
books := testGetReadBooksHandler(t, router, token, 200)
assert.Equal(t, 2, len(books))
}
func TestGetReadBooksHandler_CheckOneBook(t *testing.T) {
router := testutils.TestSetup()
token := testutils.ConnectDemo2User(router)
books := testGetReadBooksHandler(t, router, token, 200)
var book bookUserGet
for _, b := range books {
if b.Title == "De sang-froid" {
book = b
}
}
assert.Equal(t,
bookUserGet{
BookId: 18,
Title: "De sang-froid",
Author: "Truman Capote",
Rating: 6,
Read: true,
}, book)
}
func testGetReadBooksHandler(t *testing.T, router *gin.Engine, userToken string, expectedCode int) []bookUserGet {
return testGetbooksHandler(t, router, userToken, expectedCode, "/mybooks/read")
}

View File

@@ -7,7 +7,6 @@ import (
"net/http/httptest"
"testing"
"git.artlef.fr/PersonalLibraryManager/internal/testutils"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
@@ -22,45 +21,8 @@ type bookUserGet struct {
WantRead bool `json:"wantread"`
}
func TestGetBooksHandler_Demo(t *testing.T) {
router := testutils.TestSetup()
token := testutils.ConnectDemoUser(router)
books := testGetbooksHandler(t, router, token, 200)
assert.Equal(t, 26, len(books))
}
func TestGetBooksHandler_Demo2(t *testing.T) {
router := testutils.TestSetup()
token := testutils.ConnectDemo2User(router)
books := testGetbooksHandler(t, router, token, 200)
assert.Equal(t, 2, len(books))
}
func TestGetBooksHandler_CheckOneBook(t *testing.T) {
router := testutils.TestSetup()
token := testutils.ConnectDemo2User(router)
books := testGetbooksHandler(t, router, token, 200)
var book bookUserGet
for _, b := range books {
if b.Title == "De sang-froid" {
book = b
}
}
assert.Equal(t,
bookUserGet{
BookId: 18,
Title: "De sang-froid",
Author: "Truman Capote",
Rating: 6,
Read: true,
}, book)
}
func testGetbooksHandler(t *testing.T, router *gin.Engine, userToken string, expectedCode int) []bookUserGet {
req, _ := http.NewRequest("GET", "/mybooks", nil)
func testGetbooksHandler(t *testing.T, router *gin.Engine, userToken string, expectedCode int, url string) []bookUserGet {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", userToken))
w := httptest.NewRecorder()

View File

@@ -0,0 +1,29 @@
package apitest
import (
"testing"
"git.artlef.fr/PersonalLibraryManager/internal/testutils"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestGetWantReadBooksHandler_Demo(t *testing.T) {
router := testutils.TestSetup()
token := testutils.ConnectDemoUser(router)
books := testGetWantReadBooksHandler(t, router, token, 200)
assert.Equal(t, 2, len(books))
}
func TestGetWantReadBooksHandler_Demo2(t *testing.T) {
router := testutils.TestSetup()
token := testutils.ConnectDemo2User(router)
books := testGetWantReadBooksHandler(t, router, token, 200)
assert.Equal(t, 0, len(books))
}
func testGetWantReadBooksHandler(t *testing.T, router *gin.Engine, userToken string, expectedCode int) []bookUserGet {
return testGetbooksHandler(t, router, userToken, expectedCode, "/mybooks/wantread")
}

View File

@@ -56,15 +56,29 @@ type BookUserGet struct {
CoverPath string `json:"coverPath"`
}
func FetchBookUserGet(db *gorm.DB, userId uint) ([]BookUserGet, error) {
func FetchReadUserBook(db *gorm.DB, userId uint) ([]BookUserGet, error) {
query := fetchUserBookGet(db, userId)
query = query.Where("user_books.read IS TRUE")
var books []BookUserGet
res := query.Find(&books)
return books, res.Error
}
func FetchWantReadUserBook(db *gorm.DB, userId uint) ([]BookUserGet, error) {
query := fetchUserBookGet(db, userId)
query = query.Where("user_books.want_read IS TRUE")
var books []BookUserGet
res := query.Find(&books)
return books, res.Error
}
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.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 = query.Where("user_id = ?", userId)
res := query.Find(&books)
return books, res.Error
return query
}
func selectStaticFilesPath() string {

View File

@@ -8,12 +8,12 @@ import (
"git.artlef.fr/PersonalLibraryManager/internal/query"
)
func GetMyBooksHanderl(ac appcontext.AppContext) {
func GetMyBooksReadHandler(ac appcontext.AppContext) {
user, err := ac.GetAuthenticatedUser()
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
userbooks, err := query.FetchBookUserGet(ac.Db, user.ID)
userbooks, err := query.FetchReadUserBook(ac.Db, user.ID)
ac.C.JSON(http.StatusOK, userbooks)
}

View File

@@ -0,0 +1,19 @@
package routes
import (
"net/http"
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
"git.artlef.fr/PersonalLibraryManager/internal/query"
)
func GetMyBooksWantReadHandler(ac appcontext.AppContext) {
user, err := ac.GetAuthenticatedUser()
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
userbooks, err := query.FetchWantReadUserBook(ac.Db, user.ID)
ac.C.JSON(http.StatusOK, userbooks)
}

View File

@@ -24,8 +24,11 @@ func Setup(config *config.Config) *gin.Engine {
r.Use(middleware.Auth())
r.Static("/bookcover", config.ImageFolderPath)
bundle := i18nresource.InitializeI18n()
r.GET("/mybooks", func(c *gin.Context) {
routes.GetMyBooksHanderl(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
r.GET("/mybooks/read", func(c *gin.Context) {
routes.GetMyBooksReadHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
})
r.GET("/mybooks/wantread", func(c *gin.Context) {
routes.GetMyBooksWantReadHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
})
r.GET("/search/:searchterm", func(c *gin.Context) {
routes.GetSearchBooksHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})