#6 Tabellen-Definitionen erweitert, weiterer API-call

This commit is contained in:
Hartmut Bauer committed 2026-02-11 01:58:57 +01:00
1 parent bd63935a1e
commit bf7119f580
15 files changed
+158 -101

No files matched your search

+2 -2
View File
@@ -1,3 +1,3 @@
#auto generated #auto generated
#Tue Feb 10 18:18:54 CET 2026 #Tue Feb 10 23:59:14 CET 2026
build.number=58 build.number=62
@@ -35,85 +35,99 @@ fun Application.module() {
configureRouting() configureRouting()
/* // DB-Test /*
transaction { // DB-Test
transaction {
// create if not exists // create if not exists
SchemaUtils.create( SchemaUtils.drop(
SeriesTable, SeriesTable,
ArtistRolesTable, ArtistToBookTable,
ArtistsTable, ArtistRolesTable,
ArtistToBookTable, ArtistsTable,
BooksTable BooksTable
) )
logger.debug { "Tables created" } SchemaUtils.create(
val auth = ArtistRoleDAO.new { role = "Autor" } SeriesTable,
ArtistRoleDAO.new { role = "Übersetzer" } ArtistRolesTable,
ArtistsTable,
ArtistToBookTable,
BooksTable
)
logger.debug { "Tables created" }
val auth = ArtistRoleDAO.new { role = "Autor" }
ArtistRoleDAO.new { role = "Übersetzer" }
val kia1 = ArtistDAO.new { val kia1 = ArtistDAO.new {
name = "Aigner" name = "Aigner"
givenName = "Sebastian" givenName = "Sebastian"
} }
val kia2 = ArtistDAO.new { val kia2 = ArtistDAO.new {
name = "Elizarov" name = "Elizarov"
givenName = "Roman" givenName = "Roman"
} }
val kia3 = ArtistDAO.new { val kia3 = ArtistDAO.new {
name = "Isakova" name = "Isakova"
givenName = "Svetlana" givenName = "Svetlana"
} }
val kia4 = ArtistDAO.new { val kia4 = ArtistDAO.new {
name = "Jemerov" name = "Jemerov"
givenName = "Dmitry" givenName = "Dmitry"
} }
val kp1 = ArtistDAO.new { val kp1 = ArtistDAO.new {
name = "Soshin" name = "Soshin"
givenName = "Alexey" givenName = "Alexey"
} }
val kia = BookDAO.new { val kia = BookDAO.new {
title = "Kotlin in Action, 2nd Edition" title = "Kotlin in Action, 2nd Edition"
seriesIndex = 0 originalTitle = ""
} year = 2024
val kp = BookDAO.new { seriesIndex = 0
title = "Kotlin Design Patterns and Best Practices" language = Languages.EN.name
seriesIndex = 0 }
} val kp = BookDAO.new {
title = "Kotlin Design Patterns and Best Practices, 3rd Edition"
originalTitle = ""
year = 2024
seriesIndex = 0
language = Languages.EN.name
}
ArtistToBookDAO.new { ArtistToBookDAO.new {
book = kia book = kia
role = auth role = auth
artist = kia1 artist = kia1
} }
ArtistToBookDAO.new { ArtistToBookDAO.new {
book = kia book = kia
role = auth role = auth
artist = kia2 artist = kia2
} }
ArtistToBookDAO.new { ArtistToBookDAO.new {
book = kia book = kia
role = auth role = auth
artist = kia3 artist = kia3
} }
ArtistToBookDAO.new { ArtistToBookDAO.new {
book = kia book = kia
role = auth role = auth
artist = kia4 artist = kia4
} }
ArtistToBookDAO.new { ArtistToBookDAO.new {
book = kp book = kp
role = auth role = auth
artist = kp1 artist = kp1
} }
val books = BookDAO.all().map(::daoToModel) val books = BookDAO.all().map(::daoToModel)
books.forEach { book -> books.forEach { book ->
logger.debug { "### ${book.title}:" } logger.debug { "### ${book.title}:" }
book.artists.forEach { atb -> book.artists.forEach { atb ->
logger.debug { "### ${atb.role.role}: ${atb.artist.givenName} ${atb.artist.name}" } logger.debug { "### ${atb.role.role}: ${atb.artist.givenName} ${atb.artist.name}" }
}
} }
} }
}
*/ */
} }
@@ -3,14 +3,14 @@ package net.keinekekse.app.server
import net.keinekekse.version.Version import net.keinekekse.version.Version
object VERSION: Version { object VERSION: Version {
override val version = "0.1.0-beta.1+build.58" override val version = "0.1.0-beta.1+build.62"
override val fullVersion = "0.1.0-beta.1+build.58" override val fullVersion = "0.1.0-beta.1+build.62"
override val major = 0 override val major = 0
override val minor = 1 override val minor = 1
override val patch = 0 override val patch = 0
override val preRelease = "beta.1" override val preRelease = "beta.1"
override val buildMetadata = "build.58" override val buildMetadata = "build.62"
override val buildDate = "2026-02-10T17:18:54.134684Z" override val buildDate = "2026-02-10T22:59:14.710093Z"
override val displayName = "kAS" override val displayName = "kAS"
override val copyright = "Copyright (c) 2026 Hartmut Bauer" override val copyright = "Copyright (c) 2026 Hartmut Bauer"
override val name = "server" override val name = "server"
@@ -11,10 +11,9 @@ import org.koin.ktor.ext.inject
fun Route.booksRoute() { fun Route.booksRoute() {
val repository: BookRepository by inject<BookRepository>() val repository: BookRepository by inject<BookRepository>()
authenticate(UserRoles.BOOKS_USER.roleName) { authenticate(UserRoles.BOOKS_USER.name) {
route("/books") { route("/books") {
get("/books") { get("") {
// call.respondText("Test DB route")
val books = repository.allBooks() val books = repository.allBooks()
call.respond(books) call.respond(books)
} }
@@ -1,13 +1,14 @@
package net.keinekekse.app.server.books.db package net.keinekekse.app.server.books.db
import net.keinekekse.app.books.model.Artist
import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
object ArtistsTable : IntIdTable("artists") { object ArtistsTable : IntIdTable("artists") {
var name = varchar("name", 50) var name = varchar("name", 100)
var givenName = varchar("given_name", 50) var givenName = varchar("given_name", 100)
} }
class ArtistDAO(id: EntityID<Int>) : IntEntity(id) { class ArtistDAO(id: EntityID<Int>) : IntEntity(id) {
@@ -17,4 +18,4 @@ class ArtistDAO(id: EntityID<Int>) : IntEntity(id) {
var givenName by ArtistsTable.givenName var givenName by ArtistsTable.givenName
} }
//fun daoToModel(dao: ArtistDAO) = Artist(dao.name, dao.givenName) fun daoToModel(dao: ArtistDAO) = Artist(dao.id.value, dao.name, dao.givenName)
@@ -1,5 +1,6 @@
package net.keinekekse.app.server.books.db package net.keinekekse.app.server.books.db
import net.keinekekse.app.books.model.ArtistRole
import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntity
@@ -14,3 +15,5 @@ class ArtistRoleDAO(id: EntityID<Int>) : IntEntity(id) {
var role by ArtistRolesTable.role var role by ArtistRolesTable.role
} }
fun daoToModel(dao: ArtistRoleDAO) = ArtistRole(dao.id.value, dao.role)
@@ -7,7 +7,10 @@ import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
object BooksTable : IntIdTable("books") { object BooksTable : IntIdTable("books") {
var title = varchar("title", 50) var title = varchar("title", 100)
var originalTitle = varchar("original_title", 100)
var year = integer("year")
var language = varchar("language", 2)
var series = optReference("series_id", SeriesTable.id) var series = optReference("series_id", SeriesTable.id)
var seriesIndex = integer("seriesIndex") var seriesIndex = integer("seriesIndex")
} }
@@ -17,6 +20,9 @@ class BookDAO(id: EntityID<Int>) : IntEntity(id) {
var title by BooksTable.title var title by BooksTable.title
var seriesIndex by BooksTable.seriesIndex var seriesIndex by BooksTable.seriesIndex
var originalTitle by BooksTable.originalTitle
var year by BooksTable.year
var language: Languages by BooksTable.language.transform({ it.name }, { Languages.valueOf(it) })
var series by SeriesDAO.Companion optionalReferencedOn BooksTable.series var series by SeriesDAO.Companion optionalReferencedOn BooksTable.series
val artists by ArtistToBookDAO referrersOn ArtistToBookTable.bookId val artists by ArtistToBookDAO referrersOn ArtistToBookTable.bookId
@@ -25,9 +31,12 @@ class BookDAO(id: EntityID<Int>) : IntEntity(id) {
fun daoToModel(dao: BookDAO) = Book( fun daoToModel(dao: BookDAO) = Book(
dao.id.value, dao.id.value,
dao.title, dao.title,
if (dao.series != null) Series(dao.series!!.id.value, dao.series!!.name) else null, dao.originalTitle,
dao.seriesIndex, dao.year,
dao.artists.map { dao.language,
series = if (dao.series != null) Series(dao.series!!.id.value, dao.series!!.name) else null,
seriesIndex = dao.seriesIndex,
artists = dao.artists.map {
ArtistForBook( ArtistForBook(
Artist(it.artist.id.value, it.artist.name, it.artist.givenName), Artist(it.artist.id.value, it.artist.name, it.artist.givenName),
ArtistRole(it.role.id.value, it.role.role) ArtistRole(it.role.id.value, it.role.role)
@@ -1,12 +1,13 @@
package net.keinekekse.app.server.books.db package net.keinekekse.app.server.books.db
import net.keinekekse.app.books.model.Series
import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
object SeriesTable : IntIdTable("series") { object SeriesTable : IntIdTable("series") {
var name = varchar("name", 50) var name = varchar("name", 100)
} }
class SeriesDAO(id: EntityID<Int>) : IntEntity(id) { class SeriesDAO(id: EntityID<Int>) : IntEntity(id) {
@@ -14,3 +15,5 @@ class SeriesDAO(id: EntityID<Int>) : IntEntity(id) {
var name by SeriesTable.name var name by SeriesTable.name
} }
fun daoToModel(dao: SeriesDAO) = Series(dao.id.value, dao.name)
@@ -4,7 +4,9 @@ import net.keinekekse.app.books.model.Artist
import net.keinekekse.app.books.model.Book import net.keinekekse.app.books.model.Book
import net.keinekekse.app.books.model.Series import net.keinekekse.app.books.model.Series
import net.keinekekse.app.server.books.db.BookDAO import net.keinekekse.app.server.books.db.BookDAO
import net.keinekekse.app.server.books.db.BooksTable
import net.keinekekse.app.server.books.db.daoToModel import net.keinekekse.app.server.books.db.daoToModel
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.transactions.suspendTransaction import org.jetbrains.exposed.v1.jdbc.transactions.suspendTransaction
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
@@ -18,8 +20,8 @@ class BookRepositoryImpl : BookRepository {
BookDAO.findById(id)?.let { daoToModel(it) } BookDAO.findById(id)?.let { daoToModel(it) }
} }
override suspend fun booksBySeries(series: Series): List<Book> { override suspend fun booksBySeries(series: Series): List<Book> = suspendTransaction {
TODO("Not yet implemented") BookDAO.find { BooksTable.series eq series.id }.map(::daoToModel)
} }
override suspend fun bookByTitle(title: String): List<Book> { override suspend fun bookByTitle(title: String): List<Book> {
@@ -13,7 +13,10 @@ class AppPrincipal(credential: JWTCredential) : Principal {
init { init {
val realmAccess: Map<*, *>? = credential.getClaim("realm_access", Map::class) val realmAccess: Map<*, *>? = credential.getClaim("realm_access", Map::class)
val list = realmAccess?.get("roles") val list = realmAccess?.get("roles")
roles = if (list is List<*>) list.map { it as String } else emptyList() roles = if (list is List<*>) list.map {
// für den Vergleich mit der UserRoles-enum umwandeln
(it as String).uppercase().replace("-", "_")
} else emptyList()
} }
override fun getName(): String { override fun getName(): String {
@@ -13,7 +13,7 @@ private val logger = KotlinLogging.logger {}
fun Application.configureRouting() { fun Application.configureRouting() {
routing { routing {
authenticate(UserRoles.BOOKS_USER.roleName) { authenticate(UserRoles.BOOKS_USER.name) {
get("/") { get("/") {
val principal = call.principal<AppPrincipal>() val principal = call.principal<AppPrincipal>()
call.respondText("user: ${principal?.userId}") call.respondText("user: ${principal?.userId}")
@@ -27,7 +27,7 @@ fun Application.configureSecurity() {
authentication { authentication {
for (role in UserRoles.entries) { for (role in UserRoles.entries) {
jwt(role.roleName) { jwt(role.name) {
verifier( verifier(
UrlJwkProvider(URI(certUrl).toURL()), issuer UrlJwkProvider(URI(certUrl).toURL()), issuer
) { ) {
@@ -39,8 +39,8 @@ fun Application.configureSecurity() {
} }
validate { credential -> validate { credential ->
val principal = AppPrincipal(credential) val principal = AppPrincipal(credential)
if (principal.roles.contains(role.roleName) || if (principal.roles.contains(role.name) ||
principal.roles.contains(UserRoles.ADMIN.roleName) principal.roles.contains(UserRoles.ADMIN.name)
) principal else null ) principal else null
} }
} }
@@ -1,9 +1,22 @@
package net.keinekekse.app package net.keinekekse.app
enum class UserRoles(val roleName: String) { /**
ADMIN("admin"), * Die enum spiegelt die verfügbaren Rollen aus Keycloak wider.
BOOKS_ADMIN("books-admin"), *
BOOKS_USER("books-user"), * Für den Vergleich wird die Keycloak-Rolle in Großbuchstaben umgewandelt
BOOKS_EDITOR("books-editor") * und "-" durch "_" ersetzt.
*
* Alle Änderungen auf der Keycloak-Seite müssen hier nachgefahren werden.
*
* Wegen der Art des Vergleichs (für eine Route wird immer nur eine Rolle geprüft)
* ist zu beachten, dass der User jeweils alle benötigten Rollen bekommt. Implizit
* ist nur Admin, der für alle Routen zugelassen ist.
*
*/
enum class UserRoles {
ADMIN,
BOOKS_ADMIN,
BOOKS_USER,
BOOKS_EDITOR
} }
@@ -9,6 +9,9 @@ data class ArtistForBook(val artist: Artist, val role: ArtistRole)
data class Book( data class Book(
val id: Int = 0, val id: Int = 0,
val title: String = "", val title: String = "",
val originalTitle: String = "",
val year: Int = 0,
val language: Languages = Languages.DE,
val series: Series? = null, val series: Series? = null,
val seriesIndex: Int = 0, val seriesIndex: Int = 0,
@@ -0,0 +1,7 @@
package net.keinekekse.app.books.model
enum class Languages {
DE,
EN,
FR,
}