Add basic author form

This commit is contained in:
2025-11-25 14:14:24 +01:00
parent 3cbe9f909e
commit 624dfe0faa
10 changed files with 138 additions and 3 deletions

View File

@@ -36,7 +36,9 @@ INSERT INTO static_files(name, path) VALUES ('Recherches-philosophiques.jpg', 'R
INSERT INTO static_files(name, path) VALUES ('le_chateau.jpg', 'le_chateau.jpg'); INSERT INTO static_files(name, path) VALUES ('le_chateau.jpg', 'le_chateau.jpg');
-- authors -- authors
INSERT INTO authors(created_at, name) VALUES ('NOW', 'Jean-Patrick Manchette'); INSERT INTO authors(created_at, name, description) VALUES ('NOW', 'Jean-Patrick Manchette', 'Jean-Patrick Manchette, né le 19 décembre 1942 à Marseille et mort le 3 juin 1995 à Paris 12e, est un écrivain français, auteur de romans policiers, critique littéraire et de cinéma, scénariste et dialoguiste de cinéma, et traducteur.
Considéré comme l''un des auteurs les plus marquants du polar français des années 1970-1980, il est également connu pour ses opinions libertaires d''extrême gauche, proches de l''Internationale situationniste. Sur la couverture de la plupart de ses ouvrages, il est crédité en tant que J.P. Manchette, ou J-P Manchette.');
INSERT INTO authors(created_at, name) VALUES ('NOW', 'Louis-Ferdinand Céline'); INSERT INTO authors(created_at, name) VALUES ('NOW', 'Louis-Ferdinand Céline');
INSERT INTO authors(created_at, name) VALUES ('NOW', 'Anatole France'); INSERT INTO authors(created_at, name) VALUES ('NOW', 'Anatole France');
INSERT INTO authors(created_at, name) VALUES ('NOW', 'Marguerite Duras'); INSERT INTO authors(created_at, name) VALUES ('NOW', 'Marguerite Duras');

30
front/src/AuthorForm.vue Normal file
View File

@@ -0,0 +1,30 @@
<script setup>
import { ref } from 'vue'
import { getAuthor } from './api.js'
import { onBeforeRouteUpdate } from 'vue-router'
const props = defineProps({
id: String
});
let data = ref(null);
let error = ref(null);
getAuthor(data, error, props.id);
onBeforeRouteUpdate(async (to, from) => {
getAuthor(data, error, to.params.id);
})
</script>
<template>
<div v-if="error">{{$t('authorform.error', {err: error.message})}}</div>
<div v-if="data">
<h3 class="title">{{data.name}}</h3>
<p v-if="data.description">{{data.description}}</p>
</div>
</template>
<style scoped>
</style>

View File

@@ -41,6 +41,10 @@ export function getSearchBooks(data, error, searchterm, limit, offset) {
return useFetch(data, error, baseUrl + '/search/' + encodeURIComponent(searchterm) + "?" + queryParams.toString()); return useFetch(data, error, baseUrl + '/search/' + encodeURIComponent(searchterm) + "?" + queryParams.toString());
} }
export function getAuthor(data, error, id) {
return useFetch(data, error, baseUrl + '/author/' + id);
}
export function getBook(data, error, id) { export function getBook(data, error, id) {
return useFetch(data, error, baseUrl + '/book/' + id); return useFetch(data, error, baseUrl + '/book/' + id);
} }

View File

@@ -43,6 +43,9 @@
"startread": "Start reading", "startread": "Start reading",
"wantread": "I want to read it" "wantread": "I want to read it"
}, },
"authorform": {
"error": "Erreur when loading author: {error}"
},
"bookform": { "bookform": {
"error": "Error when loading book: {error}", "error": "Error when loading book: {error}",
"read": "Read", "read": "Read",

View File

@@ -43,6 +43,9 @@
"startread": "Commencer la lecture", "startread": "Commencer la lecture",
"wantread": "Je veux le lire" "wantread": "Je veux le lire"
}, },
"authorform": {
"error": "Erreur pendant le chargement de l'auteur: {error}"
},
"bookform": { "bookform": {
"error": "Erreur pendant le chargement du livre: {error}", "error": "Erreur pendant le chargement du livre: {error}",
"read": "Lu", "read": "Lu",

View File

@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import BooksBrowser from './BooksBrowser.vue' import BooksBrowser from './BooksBrowser.vue'
import AddBook from './AddBook.vue' import AddBook from './AddBook.vue'
import AuthorForm from './AuthorForm.vue'
import BookForm from './BookForm.vue' import BookForm from './BookForm.vue'
import SignUp from './SignUp.vue' import SignUp from './SignUp.vue'
import LogIn from './LogIn.vue' import LogIn from './LogIn.vue'
@@ -13,11 +14,11 @@ const routes = [
{ path: '/', component: Home }, { path: '/', component: Home },
{ path: '/books', component: BooksBrowser }, { path: '/books', component: BooksBrowser },
{ path: '/book/:id', component: BookForm, props: true }, { path: '/book/:id', component: BookForm, props: true },
{ path: '/author/:id', component: AuthorForm, props: true },
{ path: '/search/:searchterm', component: SearchBook, props: true }, { path: '/search/:searchterm', component: SearchBook, props: true },
{ path: '/add', component: AddBook }, { path: '/add', component: AddBook },
{ path: '/signup', component: SignUp }, { path: '/signup', component: SignUp },
{ path: '/login', component: LogIn }, { path: '/login', component: LogIn },
{ path: '/book', component: LogIn },
] ]
export const router = createRouter({ export const router = createRouter({

View File

@@ -0,0 +1,51 @@
package apitest
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"git.artlef.fr/PersonalLibraryManager/internal/testutils"
"github.com/stretchr/testify/assert"
)
type fetchedAuthor struct {
Name string
Description string
}
func TestGetAuthor_Ok(t *testing.T) {
author := testGetAuthor(t, "1", http.StatusOK)
expectedDescription := `Jean-Patrick Manchette, né le 19 décembre 1942 à Marseille et mort le 3 juin 1995 à Paris 12e, est un écrivain français, auteur de romans policiers, critique littéraire et de cinéma, scénariste et dialoguiste de cinéma, et traducteur.
Considéré comme l'un des auteurs les plus marquants du polar français des années 1970-1980, il est également connu pour ses opinions libertaires d'extrême gauche, proches de l'Internationale situationniste. Sur la couverture de la plupart de ses ouvrages, il est crédité en tant que J.P. Manchette, ou J-P Manchette.`
assert.Equal(t, fetchedAuthor{Name: "Jean-Patrick Manchette", Description: expectedDescription}, author)
}
func TestGetAuthor_IdNotFound(t *testing.T) {
testGetAuthor(t, "46544", http.StatusNotFound)
}
func TestGetAuthor_IdNotInt(t *testing.T) {
testGetAuthor(t, "wrong", http.StatusBadRequest)
}
func testGetAuthor(t *testing.T, authorId string, status int) fetchedAuthor {
router := testutils.TestSetup()
token := testutils.ConnectDemoUser(router)
req, _ := http.NewRequest("GET", "/author/"+authorId, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
var author fetchedAuthor
err := json.Unmarshal(w.Body.Bytes(), &author)
if err != nil {
t.Error(err)
}
assert.Equal(t, status, w.Code)
return author
}

View File

@@ -0,0 +1,38 @@
package routes
import (
"net/http"
"strconv"
"git.artlef.fr/PersonalLibraryManager/internal/appcontext"
"git.artlef.fr/PersonalLibraryManager/internal/model"
"git.artlef.fr/PersonalLibraryManager/internal/myvalidator"
"github.com/gin-gonic/gin"
)
type AuthorGet struct {
Name string `json:"name" binding:"required,max=100"`
Description string `json:"description"`
}
func GetAuthorHandler(ac appcontext.AppContext) {
authorId, err := strconv.ParseUint(ac.C.Param("id"), 10, 64)
if err != nil {
ac.C.JSON(http.StatusBadRequest, gin.H{"error": err})
return
}
err = myvalidator.ValidateId(ac.Db, uint(authorId), &model.Author{})
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
var author model.Author
res := ac.Db.First(&author, authorId)
if res.Error != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, res.Error)
return
}
ac.C.JSON(http.StatusOK, AuthorGet{
Name: author.Name,
Description: author.Description})
}

View File

@@ -29,7 +29,7 @@ func GetBookHandler(ac appcontext.AppContext) {
} }
book, queryErr := query.FetchBookGet(ac.Db, user.ID, bookId) book, queryErr := query.FetchBookGet(ac.Db, user.ID, bookId)
if queryErr != nil { if queryErr != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err) myvalidator.ReturnErrorsAsJsonResponse(&ac, queryErr)
return return
} }
ac.C.JSON(http.StatusOK, book) ac.C.JSON(http.StatusOK, book)

View File

@@ -66,6 +66,9 @@ func Setup(config *config.Config) *gin.Engine {
r.POST("/book", func(c *gin.Context) { r.POST("/book", func(c *gin.Context) {
routes.PostBookHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config}) routes.PostBookHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
}) })
r.GET("/author/:id", func(c *gin.Context) {
routes.GetAuthorHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
})
r.POST("/auth/signup", func(c *gin.Context) { r.POST("/auth/signup", func(c *gin.Context) {
routes.PostSignupHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config}) routes.PostSignupHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
}) })