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> <script setup>
import { ref } from 'vue'
import BookCard from './BookCard.vue'; import BookCard from './BookCard.vue';
import { getMyBooks } from './api.js' 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> </script>
<template> <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 class="books">
<div v-if="error">{{$t('bookbrowser.error', {error: error.message})}}</div> <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"> <div class="book" v-else-if="data" v-for="book in data" :key="book.id">
<BookCard v-bind="book" /> <BookCard v-bind="book" />

View File

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

View File

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

View File

@@ -28,7 +28,9 @@
}, },
"bookbrowser": { "bookbrowser": {
"error": "Erreur pendant le chargement des livres: {error}", "error": "Erreur pendant le chargement des livres: {error}",
"loading": "Chargement..." "loading": "Chargement...",
"read": "Lu",
"wantread": "À lire"
}, },
"searchbook": { "searchbook": {
"error": "Erreur pendant le chargement des livres: {error}", "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" "net/http/httptest"
"testing" "testing"
"git.artlef.fr/PersonalLibraryManager/internal/testutils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -22,45 +21,8 @@ type bookUserGet struct {
WantRead bool `json:"wantread"` WantRead bool `json:"wantread"`
} }
func TestGetBooksHandler_Demo(t *testing.T) { func testGetbooksHandler(t *testing.T, router *gin.Engine, userToken string, expectedCode int, url string) []bookUserGet {
router := testutils.TestSetup() req, _ := http.NewRequest("GET", url, nil)
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)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", userToken)) req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", userToken))
w := httptest.NewRecorder() 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"` 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 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 := 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, 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 books on (books.id = user_books.book_id)")
query = query.Joins("left join static_files on (static_files.id = books.cover_id)") query = query.Joins("left join static_files on (static_files.id = books.cover_id)")
query = query.Where("user_id = ?", userId) query = query.Where("user_id = ?", userId)
res := query.Find(&books) return query
return books, res.Error
} }
func selectStaticFilesPath() string { func selectStaticFilesPath() string {

View File

@@ -8,12 +8,12 @@ import (
"git.artlef.fr/PersonalLibraryManager/internal/query" "git.artlef.fr/PersonalLibraryManager/internal/query"
) )
func GetMyBooksHanderl(ac appcontext.AppContext) { func GetMyBooksReadHandler(ac appcontext.AppContext) {
user, err := ac.GetAuthenticatedUser() user, err := ac.GetAuthenticatedUser()
if err != nil { if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err) myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return return
} }
userbooks, err := query.FetchBookUserGet(ac.Db, user.ID) userbooks, err := query.FetchReadUserBook(ac.Db, user.ID)
ac.C.JSON(http.StatusOK, userbooks) 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.Use(middleware.Auth())
r.Static("/bookcover", config.ImageFolderPath) r.Static("/bookcover", config.ImageFolderPath)
bundle := i18nresource.InitializeI18n() bundle := i18nresource.InitializeI18n()
r.GET("/mybooks", func(c *gin.Context) { r.GET("/mybooks/read", func(c *gin.Context) {
routes.GetMyBooksHanderl(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config}) 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) { r.GET("/search/:searchterm", func(c *gin.Context) {
routes.GetSearchBooksHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config}) routes.GetSearchBooksHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})