This commit is contained in:
Hartmut Bauer committed 2026-10-21 23:47:44 +02:00
1 parent 3d165ee610
commit e8bd1bcb97
9 files changed
+136 -58

No files matched your search

+2 -2
View File
@@ -1,3 +1,3 @@
#auto generated #auto generated
#Wed Oct 21 01:06:20 CEST 2026 #Wed Oct 21 23:44:14 CEST 2026
build.number=655 build.number=668
+6 -6
View File
@@ -4,13 +4,13 @@ import net.keinekekse.version.Version
object VERSION: Version { object VERSION: Version {
override val version = "0.2.0-alpha" 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 major = 0
override val minor = 2 override val minor = 2
override val patch = 0 override val patch = 0
override val preRelease = "alpha" override val preRelease = "alpha"
override val buildMetadata = "build.655" override val buildMetadata = "build.668"
override val buildDate = "2026-10-20T23:06:20.951648Z" override val buildDate = "2026-10-21T21:44:14.662995Z"
override val displayName = "Application Server" override val displayName = "Application Server"
override val copyright = "Copyright (c) 2026 Hartmut Bauer" override val copyright = "Copyright (c) 2026 Hartmut Bauer"
override val name = "server" override val name = "server"
@@ -18,13 +18,13 @@ object VERSION: Version {
} }
object VERSION_books: Version { object VERSION_books: Version {
override val version = "0.2.0-alpha" 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 major = 0
override val minor = 2 override val minor = 2
override val patch = 0 override val patch = 0
override val preRelease = "alpha" override val preRelease = "alpha"
override val buildMetadata = "build.655" override val buildMetadata = "build.668"
override val buildDate = "2026-10-20T23:06:20.951769Z" override val buildDate = "2026-10-21T21:44:14.663097Z"
override val displayName = "Books-DB" override val displayName = "Books-DB"
override val copyright = "Copyright (c) 2026 Hartmut Bauer" override val copyright = "Copyright (c) 2026 Hartmut Bauer"
override val name = "books" override val name = "books"
@@ -15,4 +15,6 @@ interface BookRepository {
suspend fun allArtists(): List<Artist> suspend fun allArtists(): List<Artist>
suspend fun allSeries(): List<Series> suspend fun allSeries(): List<Series>
suspend fun allTags(): List<Tag> suspend fun allTags(): List<Tag>
suspend fun allSamplers(): List<Book>
} }
@@ -58,4 +58,7 @@ class BookRepositoryImpl(val booksDb: Database) : BookRepository {
TagsDAO.all().map(::tagDaoToModel) TagsDAO.all().map(::tagDaoToModel)
} }
override suspend fun allSamplers(): List<Book> = suspendTransaction(booksDb) {
SamplerDAO.all().map { sampler -> bookDaoToModel(sampler.book) }
}
} }
@@ -20,7 +20,11 @@ object BooksTable : IntIdTable("books") {
var type = integer("type") var type = integer("type")
var read = bool("read").default(false) var read = bool("read").default(false)
var comments = varchar("comments", 255) 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() val cover = blob("cover").nullable()
} }
@@ -48,10 +52,12 @@ class BookDAO(id: EntityID<Int>) : IntEntity(id) {
val tags by TagsToBookDAO referrersOn TagsToBookTable.bookId val tags by TagsToBookDAO referrersOn TagsToBookTable.bookId
// Verweis auf Sammelband (falls dieses Buch ein Teilroman ist) // 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) // 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( 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, series = if (dao.series != null) Series(dao.series!!.id.value, dao.series!!.name) else null,
seriesIndex = dao.seriesIndex, seriesIndex = dao.seriesIndex,
dao.parentBook?.id?.value, parentBookId = dao.parent?.id?.value,
containedBooks = if (includeChildren) { containedBooks = if (includeChildren) {
dao.containedBooks.map { bookDaoToModel(it, includeChildren = false) } dao.containedBooks.map { child ->
bookDaoToModel(child, includeChildren = false)
}
} else { } else {
emptyList() emptyList()
}, },
isSampler = dao.samplerEntry != null,
artists = dao.artists.map { artists = dao.artists.map {
ArtistForBook( ArtistForBook(
@@ -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<Int>) : IntEntity(id) {
companion object : IntEntityClass<SamplerDAO>(SamplerTable)
var book by BookDAO referencedOn SamplerTable.bookId
}
@@ -23,6 +23,7 @@ data class Book(
val series: Series? = null, val series: Series? = null,
val seriesIndex: Int? = null, val seriesIndex: Int? = null,
val parentBookId: Int? = null, // ID des Sammelbands val parentBookId: Int? = null, // ID des Sammelbands
val isSampler: Boolean = false,
val containedBooks: List<Book> = emptyList(), // Enthaltene Romane val containedBooks: List<Book> = emptyList(), // Enthaltene Romane
val artists: List<ArtistForBook> = emptyList(), val artists: List<ArtistForBook> = emptyList(),
+90 -45
View File
@@ -61,7 +61,7 @@ fun FlowContent.bookEdit(
} }
div(classes = "flex flex-row mb-5 h-9") { div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30 content-center") { div(classes = "w-30 content-center") {
+"Titel:" +"Titel"
} }
input(classes = "$cssTextInput w-150") { input(classes = "$cssTextInput w-150") {
name = "title" name = "title"
@@ -71,7 +71,7 @@ fun FlowContent.bookEdit(
} }
div(classes = "flex flex-row mb-5 h-9") { div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30 content-center") { div(classes = "w-30 content-center") {
+"Originaltitel:" +"Originaltitel"
} }
input(classes = "$cssTextInput w-150") { input(classes = "$cssTextInput w-150") {
name = "originaltitle" name = "originaltitle"
@@ -81,7 +81,7 @@ fun FlowContent.bookEdit(
} }
div(classes = "flex flex-row mb-5 h-9") { div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30 content-center") { div(classes = "w-30 content-center") {
+"Untertitel:" +"Untertitel"
} }
input(classes = "$cssTextInput w-150") { input(classes = "$cssTextInput w-150") {
name = "subtitle" name = "subtitle"
@@ -105,16 +105,16 @@ fun FlowContent.bookEdit(
} }
} }
div(classes = "ml-9 pr-3 content-center") { div(classes = "w-45 pr-3 content-center text-end") {
+"Erscheinungsjahr:" +"Erscheinungsjahr"
} }
input(classes = "$cssTextInput w-16") { input(classes = "$cssTextInput w-16") {
name = "year" name = "year"
value = session.data.book.year.toString() value = session.data.book.year.toString()
} }
div(classes = "ml-8 pr-3 content-center") { div(classes = "w-57 pr-3 content-center text-end") {
+"Jahr der Originalausgabe:" +"Jahr der Originalausgabe"
} }
input(classes = "$cssTextInput w-16") { input(classes = "$cssTextInput w-16") {
name = "originalyear" name = "originalyear"
@@ -123,7 +123,7 @@ fun FlowContent.bookEdit(
} }
div(classes = "flex flex-row mb-5 h-9") { div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30 content-center") { div(classes = "w-32 content-center") {
+"ISBN" +"ISBN"
} }
input(classes = "$cssTextInput w-40") { input(classes = "$cssTextInput w-40") {
@@ -131,7 +131,7 @@ fun FlowContent.bookEdit(
value = session.data.book.isbn value = session.data.book.isbn
} }
div(classes = "ml-14 pr-3 content-center") { div(classes = "w-26 pr-3 content-center text-end") {
+"Verlag" +"Verlag"
} }
select(classes = "$cssTextInput w-74") { select(classes = "$cssTextInput w-74") {
@@ -173,7 +173,7 @@ fun FlowContent.bookEdit(
div(classes = "w-30 content-center") { div(classes = "w-30 content-center") {
+"Serie" +"Serie"
} }
select(classes = "$cssTextInput w-98") { select(classes = "$cssTextInput w-100") {
name = "series" name = "series"
option { option {
@@ -194,45 +194,45 @@ fun FlowContent.bookEdit(
materialSymbolFormButton("add_box") materialSymbolFormButton("add_box")
} }
div(classes = "ml-13 pr-3 content-center") { div(classes = "w-20 pr-3 content-center text-end") {
+"Index" +"Index"
} }
input(classes = "$cssTextInput w-16") { input(classes = "$cssTextInput w-20") {
name = "seriesindex" name = "seriesindex"
value = session.data.book.seriesIndex?.toString() ?: "" value = session.data.book.seriesIndex?.toString() ?: ""
} }
} }
div(classes = "flex flex-row mb-7") { div(classes = "flex flex-row mb-7") {
div(classes = "w-30 content-center") { div(classes = "w-30 content-start pt-3") {
+"Künstler" +"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 tabIndex = "-1" // overflow setzt sonst einen Tabstopp
table { table {
thead(classes = "$cssFixedTHead ") { // thead(classes = "$cssFixedTHead ") {
tr { // tr {
th(classes = "py-3 px-2") { +"Name" } // th(classes = "py-3 px-2") { +"Name" }
th(classes = "py-3 px-2") { +"Rolle" } // th(classes = "py-3 px-2") { +"Rolle" }
th(classes = "py-3 px-2") { // th(classes = "py-3 px-2") {
button(classes = cssFormIcon) { // button(classes = cssFormIcon) {
title = "Künstler hinzufügen" // title = "Künstler hinzufügen"
materialSymbolFormButton("add_box") // materialSymbolFormButton("add_box")
} // }
} // }
} // }
} // }
tbody { tbody {
for ((artist, role) in session.data.book.artists) { for ((artist, role) in session.data.book.artists) {
tr { 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}" +"${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 +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) { button(classes = cssFormIcon) {
// href = "${navBooksEdit.second}/${book.id}" // href = "${navBooksEdit.second}/${book.id}"
materialSymbolFormButton("Edit") materialSymbolFormButton("Edit")
@@ -246,30 +246,35 @@ fun FlowContent.bookEdit(
} }
} }
} }
div(classes = "ml-10 pr-8 content-center") { button(classes = "$cssFormIcon self-start mt-2 ") {
+"Tags" title = "Künstler hinzufügen"
}
div(classes = "overflow-auto px-1 pb-1 h-40") {
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") 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 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")
// }
// }
// }
// }
tbody { tbody {
for (tag in session.data.book.tags) { for (tag in session.data.book.tags) {
tr { tr {
td(classes = "border-t-1 $borderTable px-2 py-1") { td(classes = "border-b-1 $borderTable px-2 py-1") {
+tag.tag +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) { button(classes = cssFormIcon) {
materialSymbolFormButton("Delete") 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") { div(classes = "flex flex-row mb-5") {
@@ -294,6 +304,41 @@ fun FlowContent.bookEdit(
// TODO parent // TODO parent
// evtl. switch oder <detail> // evtl. switch oder <detail>
// Teil von Sammelband _____v / enthält (-> Link) // 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") { div(classes = "flex flex-row mb-5 mr-2 justify-end w-180") {
button(classes = "$cssButtonStd block mt-8 mr-10") { button(classes = "$cssButtonStd block mt-8 mr-10") {
@@ -20,6 +20,7 @@ suspend fun dbTest(db: Database, booksRepo: BookRepository) {
logger.debug("start DB reset - drop") logger.debug("start DB reset - drop")
// create if not exists // create if not exists
SchemaUtils.drop( SchemaUtils.drop(
SamplerTable,
PublisherTable, PublisherTable,
TagsTable, TagsTable,
TagsToBookTable, TagsToBookTable,
@@ -32,6 +33,7 @@ suspend fun dbTest(db: Database, booksRepo: BookRepository) {
) )
logger.debug("create tables") logger.debug("create tables")
SchemaUtils.create( SchemaUtils.create(
SamplerTable,
PublisherTable, PublisherTable,
TagsTable, TagsTable,
TagsToBookTable, TagsToBookTable,