diff --git a/server/buildnumber.properties b/server/buildnumber.properties index 467dc88..e5de372 100644 --- a/server/buildnumber.properties +++ b/server/buildnumber.properties @@ -1,3 +1,3 @@ #auto generated -#Wed Oct 21 01:06:20 CEST 2026 -build.number=655 +#Wed Oct 21 23:44:14 CEST 2026 +build.number=668 diff --git a/server/src/main/kotlin/Version.kt b/server/src/main/kotlin/Version.kt index 92cbc68..d599a70 100644 --- a/server/src/main/kotlin/Version.kt +++ b/server/src/main/kotlin/Version.kt @@ -4,13 +4,13 @@ import net.keinekekse.version.Version object VERSION: Version { override val version = "0.2.0-alpha" - override val fullVersion = "0.2.0-alpha+build.655" + override val fullVersion = "0.2.0-alpha+build.668" override val major = 0 override val minor = 2 override val patch = 0 override val preRelease = "alpha" - override val buildMetadata = "build.655" - override val buildDate = "2026-10-20T23:06:20.951648Z" + override val buildMetadata = "build.668" + override val buildDate = "2026-10-21T21:44:14.662995Z" override val displayName = "Application Server" override val copyright = "Copyright (c) 2026 Hartmut Bauer" override val name = "server" @@ -18,13 +18,13 @@ object VERSION: Version { } object VERSION_books: Version { override val version = "0.2.0-alpha" - override val fullVersion = "0.2.0-alpha+build.655" + override val fullVersion = "0.2.0-alpha+build.668" override val major = 0 override val minor = 2 override val patch = 0 override val preRelease = "alpha" - override val buildMetadata = "build.655" - override val buildDate = "2026-10-20T23:06:20.951769Z" + override val buildMetadata = "build.668" + override val buildDate = "2026-10-21T21:44:14.663097Z" override val displayName = "Books-DB" override val copyright = "Copyright (c) 2026 Hartmut Bauer" override val name = "books" diff --git a/server/src/main/kotlin/books/db/BooksRepository.kt b/server/src/main/kotlin/books/db/BooksRepository.kt index 9dccc76..1c67a0b 100644 --- a/server/src/main/kotlin/books/db/BooksRepository.kt +++ b/server/src/main/kotlin/books/db/BooksRepository.kt @@ -15,4 +15,6 @@ interface BookRepository { suspend fun allArtists(): List suspend fun allSeries(): List suspend fun allTags(): List + suspend fun allSamplers(): List + } diff --git a/server/src/main/kotlin/books/db/BooksRepositoryImpl.kt b/server/src/main/kotlin/books/db/BooksRepositoryImpl.kt index 56e3b7e..25ddddc 100644 --- a/server/src/main/kotlin/books/db/BooksRepositoryImpl.kt +++ b/server/src/main/kotlin/books/db/BooksRepositoryImpl.kt @@ -58,4 +58,7 @@ class BookRepositoryImpl(val booksDb: Database) : BookRepository { TagsDAO.all().map(::tagDaoToModel) } + override suspend fun allSamplers(): List = suspendTransaction(booksDb) { + SamplerDAO.all().map { sampler -> bookDaoToModel(sampler.book) } + } } diff --git a/server/src/main/kotlin/books/db/schema/BookDAO.kt b/server/src/main/kotlin/books/db/schema/BookDAO.kt index 829da11..05c8cbe 100644 --- a/server/src/main/kotlin/books/db/schema/BookDAO.kt +++ b/server/src/main/kotlin/books/db/schema/BookDAO.kt @@ -20,7 +20,11 @@ object BooksTable : IntIdTable("books") { var type = integer("type") var read = bool("read").default(false) var comments = varchar("comments", 255) - var parentBook = optReference("parent_book_id", id) + + // Dieses Buch ist Teil eines Sammelbands. + // Null bedeutet: kein übergeordneter Sammelband. + var parent = optReference("parent_id", id) + val cover = blob("cover").nullable() } @@ -48,10 +52,12 @@ class BookDAO(id: EntityID) : IntEntity(id) { val tags by TagsToBookDAO referrersOn TagsToBookTable.bookId // Verweis auf Sammelband (falls dieses Buch ein Teilroman ist) - var parentBook by BookDAO optionalReferencedOn BooksTable.parentBook + var parent by BookDAO optionalReferencedOn BooksTable.parent // Enthaltene Einzelromane (falls dieses Buch der Sammelband ist) - val containedBooks by BookDAO optionalReferrersOn BooksTable.parentBook + val containedBooks by BookDAO optionalReferrersOn BooksTable.parent + + val samplerEntry by SamplerDAO optionalBackReferencedOn SamplerTable.bookId } fun bookDaoToModel(dao: BookDAO, includeChildren: Boolean = true): Book = Book( @@ -72,12 +78,15 @@ fun bookDaoToModel(dao: BookDAO, includeChildren: Boolean = true): Book = Book( series = if (dao.series != null) Series(dao.series!!.id.value, dao.series!!.name) else null, seriesIndex = dao.seriesIndex, - dao.parentBook?.id?.value, + parentBookId = dao.parent?.id?.value, containedBooks = if (includeChildren) { - dao.containedBooks.map { bookDaoToModel(it, includeChildren = false) } + dao.containedBooks.map { child -> + bookDaoToModel(child, includeChildren = false) + } } else { emptyList() }, + isSampler = dao.samplerEntry != null, artists = dao.artists.map { ArtistForBook( diff --git a/server/src/main/kotlin/books/db/schema/SamplerDAO.kt b/server/src/main/kotlin/books/db/schema/SamplerDAO.kt new file mode 100644 index 0000000..e90449c --- /dev/null +++ b/server/src/main/kotlin/books/db/schema/SamplerDAO.kt @@ -0,0 +1,16 @@ +package net.keinekekse.app.books.db.schema + +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IntIdTable +import org.jetbrains.exposed.v1.dao.IntEntity +import org.jetbrains.exposed.v1.dao.IntEntityClass + +object SamplerTable : IntIdTable("samplers") { + var bookId = reference("book_id", BooksTable.id).uniqueIndex() +} + +class SamplerDAO(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(SamplerTable) + + var book by BookDAO referencedOn SamplerTable.bookId +} diff --git a/server/src/main/kotlin/books/model/Book.kt b/server/src/main/kotlin/books/model/Book.kt index b472a4d..c5bcc5e 100644 --- a/server/src/main/kotlin/books/model/Book.kt +++ b/server/src/main/kotlin/books/model/Book.kt @@ -23,6 +23,7 @@ data class Book( val series: Series? = null, val seriesIndex: Int? = null, val parentBookId: Int? = null, // ID des Sammelbands + val isSampler: Boolean = false, val containedBooks: List = emptyList(), // Enthaltene Romane val artists: List = emptyList(), diff --git a/server/src/main/kotlin/books/ui/BookEdit.kt b/server/src/main/kotlin/books/ui/BookEdit.kt index 09825cc..f951ef8 100644 --- a/server/src/main/kotlin/books/ui/BookEdit.kt +++ b/server/src/main/kotlin/books/ui/BookEdit.kt @@ -61,7 +61,7 @@ fun FlowContent.bookEdit( } div(classes = "flex flex-row mb-5 h-9") { div(classes = "w-30 content-center") { - +"Titel:" + +"Titel" } input(classes = "$cssTextInput w-150") { name = "title" @@ -71,7 +71,7 @@ fun FlowContent.bookEdit( } div(classes = "flex flex-row mb-5 h-9") { div(classes = "w-30 content-center") { - +"Originaltitel:" + +"Originaltitel" } input(classes = "$cssTextInput w-150") { name = "originaltitle" @@ -81,7 +81,7 @@ fun FlowContent.bookEdit( } div(classes = "flex flex-row mb-5 h-9") { div(classes = "w-30 content-center") { - +"Untertitel:" + +"Untertitel" } input(classes = "$cssTextInput w-150") { name = "subtitle" @@ -105,16 +105,16 @@ fun FlowContent.bookEdit( } } - div(classes = "ml-9 pr-3 content-center") { - +"Erscheinungsjahr:" + div(classes = "w-45 pr-3 content-center text-end") { + +"Erscheinungsjahr" } input(classes = "$cssTextInput w-16") { name = "year" value = session.data.book.year.toString() } - div(classes = "ml-8 pr-3 content-center") { - +"Jahr der Originalausgabe:" + div(classes = "w-57 pr-3 content-center text-end") { + +"Jahr der Originalausgabe" } input(classes = "$cssTextInput w-16") { name = "originalyear" @@ -123,7 +123,7 @@ fun FlowContent.bookEdit( } div(classes = "flex flex-row mb-5 h-9") { - div(classes = "w-30 content-center") { + div(classes = "w-32 content-center") { +"ISBN" } input(classes = "$cssTextInput w-40") { @@ -131,7 +131,7 @@ fun FlowContent.bookEdit( value = session.data.book.isbn } - div(classes = "ml-14 pr-3 content-center") { + div(classes = "w-26 pr-3 content-center text-end") { +"Verlag" } select(classes = "$cssTextInput w-74") { @@ -173,7 +173,7 @@ fun FlowContent.bookEdit( div(classes = "w-30 content-center") { +"Serie" } - select(classes = "$cssTextInput w-98") { + select(classes = "$cssTextInput w-100") { name = "series" option { @@ -194,45 +194,45 @@ fun FlowContent.bookEdit( materialSymbolFormButton("add_box") } - div(classes = "ml-13 pr-3 content-center") { + div(classes = "w-20 pr-3 content-center text-end") { +"Index" } - input(classes = "$cssTextInput w-16") { + input(classes = "$cssTextInput w-20") { name = "seriesindex" value = session.data.book.seriesIndex?.toString() ?: "" } } div(classes = "flex flex-row mb-7") { - div(classes = "w-30 content-center") { + div(classes = "w-30 content-start pt-3") { +"Künstler" } - div(classes = "overflow-auto px-1 pb-1 h-40") { + div(classes = "overflow-auto px-1 pb-1 h-40 w-82") { tabIndex = "-1" // overflow setzt sonst einen Tabstopp table { - thead(classes = "$cssFixedTHead ") { - tr { - th(classes = "py-3 px-2") { +"Name" } - th(classes = "py-3 px-2") { +"Rolle" } - th(classes = "py-3 px-2") { - button(classes = cssFormIcon) { - title = "Künstler hinzufügen" - materialSymbolFormButton("add_box") - } - } - } - } +// thead(classes = "$cssFixedTHead ") { +// tr { +// th(classes = "py-3 px-2") { +"Name" } +// th(classes = "py-3 px-2") { +"Rolle" } +// th(classes = "py-3 px-2") { +// button(classes = cssFormIcon) { +// title = "Künstler hinzufügen" +// materialSymbolFormButton("add_box") +// } +// } +// } +// } tbody { for ((artist, role) in session.data.book.artists) { tr { - td(classes = "border-t-1 $borderTable px-2 py-1") { + td(classes = "border-b-1 $borderTable px-2 py-1") { +"${artist.name}, ${artist.givenName}" } - td(classes = "border-t-1 $borderTable px-2 py-1") { + td(classes = "border-b-1 $borderTable px-2 py-1") { +role.role } - td(classes = "border-t-1 $borderTable px-2 py-1") { + td(classes = "border-b-1 $borderTable px-2 py-1") { button(classes = cssFormIcon) { // href = "${navBooksEdit.second}/${book.id}" materialSymbolFormButton("Edit") @@ -246,30 +246,35 @@ fun FlowContent.bookEdit( } } } - div(classes = "ml-10 pr-8 content-center") { + button(classes = "$cssFormIcon self-start mt-2 ") { + title = "Künstler hinzufügen" + materialSymbolFormButton("add_box") + } + + div(classes = "w-20 pr-3 content-start text-end pt-3") { +"Tags" } - div(classes = "overflow-auto px-1 pb-1 h-40") { + div(classes = "overflow-auto px-1 pb-1 h-40 w-30") { tabIndex = "-1" // overflow setzt sonst einen Tabstopp table { - thead(classes = "$cssFixedTHead ") { - tr { - th(classes = "py-3 px-2") { +"Tag" } - th(classes = "py-3 px-2") { - button(classes = cssFormIcon) { - title = "Tag hinzufügen" - materialSymbolFormButton("add_box") - } - } - } - } +// thead(classes = "$cssFixedTHead ") { +// tr { +// th(classes = "py-3 px-2") { +"Tag" } +// th(classes = "py-3 px-2") { +// button(classes = cssFormIcon) { +// title = "Tag hinzufügen" +// materialSymbolFormButton("add_box") +// } +// } +// } +// } tbody { for (tag in session.data.book.tags) { tr { - td(classes = "border-t-1 $borderTable px-2 py-1") { + td(classes = "border-b-1 $borderTable px-2 py-1") { +tag.tag } - td(classes = "border-t-1 $borderTable px-2 py-1") { + td(classes = "border-b-1 $borderTable px-2 py-1") { button(classes = cssFormIcon) { materialSymbolFormButton("Delete") } @@ -279,6 +284,11 @@ fun FlowContent.bookEdit( } } } + button(classes = "$cssFormIcon self-start mt-2") { + title = "Tag hinzufügen" + materialSymbolFormButton("add_box") + } + } div(classes = "flex flex-row mb-5") { @@ -294,6 +304,41 @@ fun FlowContent.bookEdit( // TODO parent // evtl. switch oder // Teil von Sammelband _____v / enthält (-> Link) + div(classes = "flex flex-row mb-5 h-9") { + div(classes = "w-30 content-center") { + +"Sammelband" + } + label( + classes = "w-10 justify-center grid grid-cols-[1.3em] " + + "focus-within:bg-orange-300 hover:bg-orange-300" + ) { + input { + name = "sampler" + type = InputType.checkBox // liefert sampler=on || nix ! + checked = session.data.book.isSampler + } + } + div(classes = "w-30 content-center text-end pr-3") { + +"Teil von" + } + select(classes = "$cssTextInput w-50") { + name = "parent" + option { + value = "0" + +"---" + } +// for (serie in session.data.seriesList) { +// option { +// if (session.data.book.series != null && serie.id == session.data.book.series!!.id) +// selected = true +// value = serie.id.toString() +// +serie.name +// } +// } + + } + + } div(classes = "flex flex-row mb-5 mr-2 justify-end w-180") { button(classes = "$cssButtonStd block mt-8 mr-10") { diff --git a/server/src/main/kotlin/books/ui/DbTest.kt b/server/src/main/kotlin/books/ui/DbTest.kt index ef98854..33a5dff 100644 --- a/server/src/main/kotlin/books/ui/DbTest.kt +++ b/server/src/main/kotlin/books/ui/DbTest.kt @@ -20,6 +20,7 @@ suspend fun dbTest(db: Database, booksRepo: BookRepository) { logger.debug("start DB reset - drop") // create if not exists SchemaUtils.drop( + SamplerTable, PublisherTable, TagsTable, TagsToBookTable, @@ -32,6 +33,7 @@ suspend fun dbTest(db: Database, booksRepo: BookRepository) { ) logger.debug("create tables") SchemaUtils.create( + SamplerTable, PublisherTable, TagsTable, TagsToBookTable,