Add existing book fields in "create book" form

This commit is contained in:
2026-03-31 17:35:32 +02:00
parent c1b6b61678
commit 32d39cabcd
7 changed files with 162 additions and 19 deletions

View File

@@ -9,6 +9,11 @@ const router = useRouter()
const book = ref({
title: '',
author: '',
isbn: '',
inventaireid: '',
openlibraryid: '',
shortdescription: '',
summary: '',
coverId: null,
})
const errors = ref(null)
@@ -18,11 +23,26 @@ const titleError = computed(() => {
const authorError = computed(() => {
return extractFormErrorFromField('Author', errors.value)
})
const isbnError = computed(() => {
return extractFormErrorFromField('ISBN', errors.value)
})
const inventaireError = computed(() => {
return extractFormErrorFromField('InventaireID', errors.value)
})
const openLibraryError = computed(() => {
return extractFormErrorFromField('OpenLibraryId', errors.value)
})
const shortDescError = computed(() => {
return extractFormErrorFromField('ShortDescription', errors.value)
})
const summaryError = computed(() => {
return extractFormErrorFromField('ShortDescription', errors.value)
})
function onSubmit(e) {
postBook(book).then((res) => {
if (res.ok) {
router.push('/')
res.json().then((json) => router.push('/book/' + json.id))
return
} else {
res.json().then((json) => (errors.value = json))
@@ -37,7 +57,7 @@ function onSubmit(e) {
<label class="label">{{ $t('addbook.title') }}</label>
<div class="control">
<input
:class="'input ' + (titleError ? 'is-danger' : '')"
:class="'input is-medium ' + (titleError ? 'is-danger' : '')"
type="text"
maxlength="300"
required
@@ -51,7 +71,7 @@ function onSubmit(e) {
<label class="label">{{ $t('addbook.author') }}</label>
<div class="control">
<input
:class="'input ' + (authorError ? 'is-danger' : '')"
:class="'input is-medium ' + (authorError ? 'is-danger' : '')"
type="text"
maxlength="100"
v-model="book.author"
@@ -60,7 +80,71 @@ function onSubmit(e) {
</div>
<p v-if="authorError" class="help is-danger">{{ authorError }}</p>
</div>
<div class="field">
<label class="label">{{ $t('addbook.shortdesc') }}</label>
<div class="control">
<input
:class="'input ' + (shortDescError ? 'is-danger' : '')"
type="text"
maxlength="300"
v-model="book.shortdescription"
:placeholder="$t('addbook.shortdesc')"
/>
</div>
<p v-if="shortDescError" class="help is-danger">{{ shortDescError }}</p>
</div>
<CoverUpload name="cover" @on-image-upload="(id) => (book.coverId = id)" />
<div class="field">
<label class="label">{{ $t('addbook.summary') }}</label>
<div class="control">
<textarea
:class="'textarea ' + (summaryError ? 'is-danger' : '')"
type="text"
v-model="book.summary"
:placeholder="$t('addbook.summary')"
/>
</div>
<p v-if="summaryError" class="help is-danger">{{ summaryError }}</p>
</div>
<div class="field">
<label class="label">ISBN</label>
<div class="control">
<input
:class="'input ' + (isbnError ? 'is-danger' : '')"
type="text"
maxlength="18"
v-model="book.isbn"
placeholder="ISBN"
/>
</div>
<p v-if="isbnError" class="help is-danger">{{ isbnError }}</p>
</div>
<div class="field">
<label class="label">Inventaire</label>
<div class="control">
<input
:class="'input ' + (inventaireError ? 'is-danger' : '')"
type="text"
maxlength="50"
v-model="book.inventaireid"
placeholder="Inventaire"
/>
</div>
<p v-if="inventaireError" class="help is-danger">{{ inventaireError }}</p>
</div>
<div class="field">
<label class="label">OpenLibrary</label>
<div class="control">
<input
:class="'input ' + (openLibraryError ? 'is-danger' : '')"
type="text"
maxlength="50"
v-model="book.openlibraryid"
placeholder="OpenLibrary"
/>
</div>
<p v-if="openLibraryError" class="help is-danger">{{ openLibraryError }}</p>
</div>
<div class="field">
<div class="control">
<button class="button is-link">{{ $t('addbook.submit') }}</button>

View File

@@ -191,10 +191,7 @@ export function genericPayloadCall(apiRoute, object, method) {
}
export function extractFormErrorFromField(fieldName, errors) {
if (errors === null) {
return ''
}
if (errors.value == null) {
if (errors == null) {
return ''
}
console.log(errors.value)

View File

@@ -20,6 +20,8 @@
"addbook": {
"title": "Title",
"author": "Author",
"shortdesc": "Short description",
"summary": "Summary",
"submit": "Submit",
"coverupload": "Upload cover"
},

View File

@@ -20,6 +20,8 @@
"addbook": {
"title": "Titre",
"author": "Auteur",
"shortdesc": "Description rapide",
"summary": "Résumé",
"submit": "Confirmer",
"coverupload": "Téléverser la couverture"
},

View File

@@ -1,12 +1,15 @@
package apitest
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"git.artlef.fr/bibliomane/internal/dto"
"git.artlef.fr/bibliomane/internal/testutils"
"github.com/stretchr/testify/assert"
)
@@ -45,6 +48,36 @@ func TestPostBookHandler_noTitle(t *testing.T) {
testPostBookHandler(t, bookJson, 400)
}
func TestPostBookHandler_AllFields(t *testing.T) {
bookJson :=
`{
"author": "Kafka",
"title": "Amerika",
"isbn": "978-2-07-036803-7",
"inventaireid": "isbn:9782070368037",
"openlibraryid": "OL8838048M",
"shortdescription": "Roman de Franz Kafka",
"summary": "L'Amérique (Amerika en version originale allemande) ou Le Disparu (Der Verschollene, titre voulu par l'auteur et rendu au livre dans ses plus récentes éditions) est le premier roman de Franz Kafka (1883-1924)."
}`
id := testPostBookHandler(t, bookJson, 200)
createdBook := testGetBook(t, strconv.FormatUint(uint64(id), 10), http.StatusOK)
assert.Equal(t,
dto.FullBookGet{
Title: "Amerika",
Author: "Kafka",
AuthorID: 27,
ISBN: "978-2-07-036803-7",
InventaireId: "isbn:9782070368037",
OpenLibraryId: "OL8838048M",
Summary: "L'Amérique (Amerika en version originale allemande) ou Le Disparu (Der Verschollene, titre voulu par l'auteur et rendu au livre dans ses plus récentes éditions) est le premier roman de Franz Kafka (1883-1924).",
Rating: 0,
Read: false,
CoverPath: "",
Review: "",
}, createdBook)
}
func TestPostBookHandler_TitleTooLong(t *testing.T) {
bookJson :=
`{
@@ -63,7 +96,7 @@ func TestPostBookHandler_AuthorTooLong(t *testing.T) {
testPostBookHandler(t, bookJson, 400)
}
func testPostBookHandler(t *testing.T, bookJson string, expectedCode int) {
func testPostBookHandler(t *testing.T, bookJson string, expectedCode int) uint {
router := testutils.TestSetup()
w := httptest.NewRecorder()
@@ -75,4 +108,16 @@ func testPostBookHandler(t *testing.T, bookJson string, expectedCode int) {
assert.Equal(t, expectedCode, w.Code)
if w.Code != http.StatusOK {
return 0
}
var parsed struct {
ID uint
}
err := json.Unmarshal(w.Body.Bytes(), &parsed)
if err != nil {
t.Error(err)
}
return parsed.ID
}

View File

@@ -11,9 +11,14 @@ type BookSearchGetParam struct {
}
type BookPostCreate struct {
Title string `json:"title" binding:"required,max=300"`
Author string `json:"author" binding:"max=100"`
CoverID uint `json:"coverId"`
Title string `json:"title" binding:"required,max=300"`
Author string `json:"author" binding:"max=100"`
ISBN string `json:"isbn" binding:"max=18"`
InventaireID string `json:"inventaireid" binding:"max=50"`
OpenLibraryId string `json:"openlibraryid" binding:"max=50"`
ShortDescription string `json:"shortdescription" binding:"max=300"`
Summary string `json:"summary"`
CoverID uint `json:"coverId"`
}
type BookPostImport struct {

View File

@@ -2,11 +2,13 @@ package routes
import (
"errors"
"net/http"
"git.artlef.fr/bibliomane/internal/appcontext"
"git.artlef.fr/bibliomane/internal/dto"
"git.artlef.fr/bibliomane/internal/model"
"git.artlef.fr/bibliomane/internal/myvalidator"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
@@ -28,28 +30,34 @@ func PostBookHandler(ac appcontext.AppContext) {
return
}
err = saveBookToDb(ac, book, &user)
id, err := saveBookToDb(ac, book, &user)
if err != nil {
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
return
}
ac.C.String(200, "Success")
ac.C.JSON(http.StatusOK, gin.H{"id": id})
}
func saveBookToDb(ac appcontext.AppContext, b dto.BookPostCreate, user *model.User) error {
func saveBookToDb(ac appcontext.AppContext, b dto.BookPostCreate, user *model.User) (uint, error) {
author, err := fetchOrCreateAuthor(ac, b.Author)
if err != nil {
return err
return 0, err
}
book := model.Book{
Title: b.Title,
AuthorID: author.ID,
AddedBy: *user,
Title: b.Title,
AuthorID: author.ID,
ISBN: b.ISBN,
InventaireID: b.InventaireID,
OpenLibraryId: b.OpenLibraryId,
SmallDescription: b.ShortDescription,
Summary: b.Summary,
AddedBy: *user,
}
if b.CoverID > 0 {
book.CoverID = b.CoverID
}
return ac.Db.Save(&book).Error
err = ac.Db.Save(&book).Error
return book.ID, err
}
func fetchOrCreateAuthor(ac appcontext.AppContext, name string) (*model.Author, error) {