Integrate users and books

- new table userbook linking users and book
- moved rating to users book
- updated demo data and tests
- updated front to hide routes from non connected users
This commit is contained in:
2025-10-05 16:14:53 +02:00
parent f18e6a3ba8
commit cb1f974f02
16 changed files with 143 additions and 73 deletions

View File

@@ -35,32 +35,42 @@ func connectDemoUser(router *gin.Engine) string {
req, _ := http.NewRequest("POST", "/auth/login", strings.NewReader(loginJson))
router.ServeHTTP(w, req)
var parsedResponse loginResponse
body := w.Body.String()
err := json.Unmarshal([]byte(body), &parsedResponse)
err := json.Unmarshal(w.Body.Bytes(), &parsedResponse)
if err != nil {
log.Fatal(err)
}
return parsedResponse.Token
}
type bookUserGet struct {
Title string `json:"title" binding:"required,max=300"`
Author string `json:"author" binding:"max=100"`
Rating int `json:"rating" binding:"min=0,max=10"`
}
func TestGetBooksHandler(t *testing.T) {
router := testSetup()
w := httptest.NewRecorder()
token := connectDemoUser(router)
req, _ := http.NewRequest("GET", "/books", nil)
req, _ := http.NewRequest("GET", "/mybooks", nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
router.ServeHTTP(w, req)
var parsedResponse []bookUserGet
err := json.Unmarshal(w.Body.Bytes(), &parsedResponse)
if err != nil {
log.Fatal(err)
}
assert.Equal(t, 200, w.Code)
assert.Equal(t, 28, len(parsedResponse))
}
func TestPostBookHandler_Ok(t *testing.T) {
bookJson :=
`{
"title": "Le château",
"author": "Kafka",
"rating": 9
"author": "Kafka"
}`
testPostBookHandler(t, bookJson, 200)
}
@@ -76,18 +86,7 @@ func TestPostBookHandler_OkOnlyTitle(t *testing.T) {
func TestPostBookHandler_noTitle(t *testing.T) {
bookJson :=
`{
"author": "Kafka",
"rating": 9
}`
testPostBookHandler(t, bookJson, 400)
}
func TestPostBookHandler_WrongRating(t *testing.T) {
bookJson :=
`{
"title": "Le château",
"author": "Kafka",
"rating": 15
"author": "Kafka"
}`
testPostBookHandler(t, bookJson, 400)
}
@@ -96,8 +95,7 @@ func TestPostBookHandler_TitleTooLong(t *testing.T) {
bookJson :=
`{
"title": "Noisy outlaws, unfriendly blobs, and some other things that aren't as scary, maybe, depending on how you feel about lost lands, stray cellphones, creatures from the sky, parents who disappear in Peru, a man named Lars Farf, and one other story we couldn't quite finish, so maybe you could help us out.",
"author": "Eli Horowitz",
"rating": 2
"author": "Eli Horowitz"
}`
testPostBookHandler(t, bookJson, 400)
}
@@ -106,8 +104,7 @@ func TestPostBookHandler_AuthorTooLong(t *testing.T) {
bookJson :=
`{
"title": "something",
"author": "Wolfeschlegelsteinhausenbergerdorffwelchevoralternwarengewissenhaftschaferswessenschafewarenwohlgepflegeundsorgfaltigkeitbeschutzenvonangreifendurchihrraubgierigfeindewelchevoralternzwolftausendjahresvorandieerscheinenvanderersteerdemenschderraumschiffgebrauchlichtalsseinursprungvonkraftgestartseinlangefahrthinzwischensternartigraumaufdersuchenachdiesternwelchegehabtbewohnbarplanetenkreisedrehensichundwohinderneurassevonverstandigmenschlichkeitkonntefortpflanzenundsicherfreuenanlebenslanglichfreudeundruhemitnichteinfurchtvorangreifenvonandererintelligentgeschopfsvonhinzwischensternartigraum",
"rating": 2
"author": "Wolfeschlegelsteinhausenbergerdorffwelchevoralternwarengewissenhaftschaferswessenschafewarenwohlgepflegeundsorgfaltigkeitbeschutzenvonangreifendurchihrraubgierigfeindewelchevoralternzwolftausendjahresvorandieerscheinenvanderersteerdemenschderraumschiffgebrauchlichtalsseinursprungvonkraftgestartseinlangefahrthinzwischensternartigraumaufdersuchenachdiesternwelchegehabtbewohnbarplanetenkreisedrehensichundwohinderneurassevonverstandigmenschlichkeitkonntefortpflanzenundsicherfreuenanlebenslanglichfreudeundruhemitnichteinfurchtvorangreifenvonandererintelligentgeschopfsvonhinzwischensternartigraum"
}`
testPostBookHandler(t, bookJson, 400)
}

View File

@@ -1,29 +1,57 @@
INSERT INTO users(created_at, name, password) VALUES ('NOW', 'demo','$2a$10$7mfCBxBwIzXDU6r9az26o.zPX/r6IlNZVfU9zxSoLVtc0kRPimzba');
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'O dingos, o chateaux!','Jean-Patrick Manchette', 6);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Le petit bleu de la côte Ouest','Jean-Patrick Manchette', 7);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'D''un château l''autre','Louis-Ferdinand Céline', 10);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Les dieux ont soif','Anatole France', 7);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Rigodon','Louis-Ferdinand Céline', 10);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Un barrage contre le Pacifique','Marguerite Duras', NULL);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Salammbô','Flaubert Gustave', 8);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Langage Machine','Romain Lucazeau', 5);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'La Ville et les chiens','Mario Vargas Llosa', 7);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'La Fille Du Capitaine','A. Pouchkine', 8);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Aurélien','Louis Aragon', 8);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Duo','Colette', 9);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Gargantua','François Rabelais', 7);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'The Life of Jesus','Ernest Renan', NULL);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'L''Homme sans qualités, tome 1','Robert Musil', 7);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'The Green House','Mario Vargas Llosa', 6);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Le coup de pistolet','Alexandre Pouchkine', 8);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'De sang-froid','Truman Capote', 6);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'La position du tireur couché','Jean-Patrick Manchette', 7);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Vers le Phare','Virginia Woolf', 6);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'L''insoutenable légèreté de l''être', 'Milan Kundera', 8);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Le complot contre l''Amérique','Philip Roth', 6);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Nord','Louis-Ferdinand Céline', 10);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Sa majesté des mouches','William Golding', 5);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Le Pavillon d''or','Yukio Mishima', 8);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Le meurtre d''O-tsuya','Junichiro Tanizaki', 7);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Dojoji et autres nouvelles','Yukio Mishima', 8);
INSERT INTO books(created_at, title, author, rating) VALUES ('NOW', 'Noisy outlaws, unfriendly blobs, and some other things that aren''t as scary, maybe, depending on how you feel about lost lands, stray cellphones, creatures from the sky, parents who disappear in Peru, a man named Lars Farf, and one other story we couldn''t quite finish, so maybe you could help us out','Wolfeschlegelsteinhausenbergerdorffwelchevoralternwarengewissenhaftschaferswessenschafewarenwohlgepf', 2);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'O dingos, o chateaux!','Jean-Patrick Manchette');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'O dingos, o chateaux!') ,7);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Le petit bleu de la côte Ouest','Jean-Patrick Manchette');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Le petit bleu de la côte Ouest') ,7);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'D''un château l''autre','Louis-Ferdinand Céline');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'D''un château l''autre') ,10);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Les dieux ont soif','Anatole France');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Les dieux ont soif') ,7);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Rigodon','Louis-Ferdinand Céline');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Rigodon') ,10);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Un barrage contre le Pacifique','Marguerite Duras');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Un barrage contre le Pacifique') ,7);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Salammbô','Flaubert Gustave');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Salammbô') ,8);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Langage Machine','Romain Lucazeau');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Langage Machine') ,5);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'La Ville et les chiens','Mario Vargas Llosa');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'La Ville et les chiens'),6);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'La Fille Du Capitaine','A. Pouchkine');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'La Fille Du Capitaine'),8);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Aurélien','Louis Aragon');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Aurélien') ,8);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Duo','Colette');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Duo'),9);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Gargantua','François Rabelais');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Gargantua'),7);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'The Life of Jesus','Ernest Renan');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'The Life of Jesus'), NULL);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'L''Homme sans qualités, tome 1','Robert Musil');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'L''Homme sans qualités, tome 1') ,7);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'The Green House','Mario Vargas Llosa');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'The Green House') ,6);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Le coup de pistolet','Alexandre Pouchkine');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Le coup de pistolet'),8);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'De sang-froid','Truman Capote');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'De sang-froid'),6);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'La position du tireur couché','Jean-Patrick Manchette');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'La position du tireur couché'),6);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Vers le Phare','Virginia Woolf');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Vers le Phare'),6);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'L''insoutenable légèreté de l''être', 'Milan Kundera');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'L''insoutenable légèreté de l''être') ,8);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Le complot contre l''Amérique','Philip Roth');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Le complot contre l''Amérique'),6);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Nord','Louis-Ferdinand Céline');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Nord'),10);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Sa majesté des mouches','William Golding');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Sa majesté des mouches'),5);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Le Pavillon d''or','Yukio Mishima');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Le Pavillon d''or'),8);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Le meurtre d''O-tsuya','Junichiro Tanizaki');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Le meurtre d''O-tsuya'),7);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Dojoji et autres nouvelles','Yukio Mishima');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Dojoji et autres nouvelles'),8);
INSERT INTO books(created_at, title, author) VALUES ('NOW', 'Noisy outlaws, unfriendly blobs, and some other things that aren''t as scary, maybe, depending on how you feel about lost lands, stray cellphones, creatures from the sky, parents who disappear in Peru, a man named Lars Farf, and one other story we couldn''t quite finish, so maybe you could help us out','Wolfeschlegelsteinhausenbergerdorffwelchevoralternwarengewissenhaftschaferswessenschafewarenwohlgepf');
INSERT INTO user_books(created_at, user_id, book_id, rating) VALUES ('NOW',(SELECT id FROM users WHERE name = 'demo'),(SELECT id FROM books WHERE title = 'Noisy outlaws, unfriendly blobs, and some other things that aren''t as scary, maybe, depending on how you feel about lost lands, stray cellphones, creatures from the sky, parents who disappear in Peru, a man named Lars Farf, and one other story we couldn''t quite finish, so maybe you could help us out'),2);

View File

@@ -17,15 +17,12 @@
<RouterLink to="/" class="navbar-item" activeClass="is-active">
Home
</RouterLink>
<RouterLink to="/add" class="navbar-item" activeClass="is-active">
<RouterLink v-if="authStore.user" to="/add" class="navbar-item" activeClass="is-active">
Add Book
</RouterLink>
<a class="navbar-item">
Books
</a>
<a class="navbar-item">
Locations
</a>
<RouterLink v-if="authStore.user" to="/books" class="navbar-item" activeClass="is-active">
My Books
</RouterLink>
</div>
<div class="navbar-end">
</div>

View File

@@ -1,8 +1,8 @@
<script setup>
import BookCard from './BookCard.vue';
import { getBooks } from './api.js'
import { getMyBooks } from './api.js'
const { data, error } = getBooks();
const { data, error } = getMyBooks();
</script>

19
front/src/Home.vue Normal file
View File

@@ -0,0 +1,19 @@
<script setup>
import { useAuthStore } from './auth.store.js'
const authStore = useAuthStore();
</script>
<template>
<div v-if="authStore.user">
Welcome {{ authStore.user.username }} !
</div>
<div v-else>
Welcome to PersonalLibraryManager. Please login ou sign in to continue.
</div>
</template>
<style scoped>
</style>

View File

@@ -24,8 +24,8 @@ function useFetch(url) {
return { data, error }
}
export function getBooks() {
return useFetch(baseUrl + '/books');
export function getMyBooks() {
return useFetch(baseUrl + '/mybooks');
}
export function postBook(book) {

View File

@@ -6,10 +6,12 @@ import BooksBrowser from './BooksBrowser.vue'
import AddBook from './AddBook.vue'
import SignUp from './SignUp.vue'
import LogIn from './LogIn.vue'
import Home from './Home.vue'
const routes = [
{ path: '/', component: BooksBrowser },
{ path: '/', component: Home },
{ path: '/books', component: BooksBrowser },
{ path: '/add', component: AddBook },
{ path: '/signup', component: SignUp },
{ path: '/login', component: LogIn },

View File

@@ -3,7 +3,6 @@ package api
type bookPostCreate struct {
Title string `json:"title" binding:"required,max=300"`
Author string `json:"author" binding:"max=100"`
Rating int `json:"rating" binding:"min=0,max=10"`
}
type userSignup struct {
@@ -15,3 +14,9 @@ type userLogin struct {
Username string `json:"username" binding:"required,min=2,max=20"`
Password string `json:"password" binding:"required,min=6,max=100"`
}
type bookUserGet struct {
Title string `json:"title" binding:"required,max=300"`
Author string `json:"author" binding:"max=100"`
Rating int `json:"rating" binding:"min=0,max=10"`
}

View File

@@ -9,6 +9,13 @@ func (b bookPostCreate) toBook() model.Book {
return model.Book{
Title: b.Title,
Author: b.Author,
}
}
func fromUserBookDb(b *model.UserBook) bookUserGet {
return bookUserGet{
Title: b.Book.Title,
Author: b.Book.Author,
Rating: b.Rating,
}
}

View File

@@ -13,10 +13,14 @@ import (
"gorm.io/gorm"
)
func GetBooksHanderl(c *gin.Context, db *gorm.DB) {
var books []model.Book
db.Model(&model.Book{}).Find(&books)
c.JSON(http.StatusOK, books)
func GetMyBooksHanderl(c *gin.Context, db *gorm.DB) {
var userbooks []model.UserBook
db.Preload("Book").Find(&userbooks)
var booksDto []bookUserGet
for _, userbook := range userbooks {
booksDto = append(booksDto, fromUserBookDb(&userbook))
}
c.JSON(http.StatusOK, booksDto)
}
func PostBookHandler(c *gin.Context, db *gorm.DB) {

View File

@@ -20,6 +20,7 @@ func Initdb(databasePath string, demoDataPath string) *gorm.DB {
// Migrate the schema
db.AutoMigrate(&model.Book{})
db.AutoMigrate(&model.User{})
db.AutoMigrate(&model.UserBook{})
var book model.Book
queryResult := db.Limit(1).Find(&book)
if queryResult.RowsAffected == 0 && demoDataPath != "" {

View File

@@ -1,7 +1,6 @@
package middleware
import (
"fmt"
"net/http"
"strings"
@@ -20,7 +19,6 @@ func Auth() gin.HandlerFunc {
username, err := parseUserFromJwt(c)
if err != nil {
fmt.Println(err)
c.AbortWithStatusJSON(http.StatusUnauthorized,
gin.H{"error": "You must be logged in to access this resource."})
} else {

View File

@@ -6,5 +6,4 @@ type Book struct {
gorm.Model
Title string `json:"title" gorm:"not null"`
Author string `json:"author"`
Rating int `json:"rating"`
}

View File

@@ -6,4 +6,5 @@ type User struct {
gorm.Model
Name string `gorm:"index;uniqueIndex"`
Password string
UserBooks []UserBook
}

View File

@@ -0,0 +1,12 @@
package model
import "gorm.io/gorm"
// describes the relationship between a user and a book.
type UserBook struct {
gorm.Model
UserID uint
BookID uint
Book Book
Rating int
}

View File

@@ -26,8 +26,8 @@ func setup(config *config.Config) *gin.Engine {
r := gin.Default()
r.Use(cors.New(configureCors())) // All origins allowed by default
r.Use(middleware.Auth())
r.GET("/books", func(c *gin.Context) {
api.GetBooksHanderl(c, db)
r.GET("/mybooks", func(c *gin.Context) {
api.GetMyBooksHanderl(c, db)
})
r.POST("/book", func(c *gin.Context) {
api.PostBookHandler(c, db)