progress
This commit is contained in:
1 parent
3d165ee610
commit
e8bd1bcb97
9 files changed
+135
-57
No files matched your search
@@ -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
|
||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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 ") {
|
||||||
|
title = "Künstler hinzufügen"
|
||||||
|
materialSymbolFormButton("add_box")
|
||||||
|
}
|
||||||
|
|
||||||
|
div(classes = "w-20 pr-3 content-start text-end pt-3") {
|
||||||
+"Tags"
|
+"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
|
tabIndex = "-1" // overflow setzt sonst einen Tabstopp
|
||||||
table {
|
table {
|
||||||
thead(classes = "$cssFixedTHead ") {
|
// thead(classes = "$cssFixedTHead ") {
|
||||||
tr {
|
// tr {
|
||||||
th(classes = "py-3 px-2") { +"Tag" }
|
// th(classes = "py-3 px-2") { +"Tag" }
|
||||||
th(classes = "py-3 px-2") {
|
// th(classes = "py-3 px-2") {
|
||||||
button(classes = cssFormIcon) {
|
// button(classes = cssFormIcon) {
|
||||||
title = "Tag hinzufügen"
|
// title = "Tag hinzufügen"
|
||||||
materialSymbolFormButton("add_box")
|
// 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,
|
||||||
|
|||||||
Reference in new issue
Block a user