#6 Tabellen-Definitionen erweitert, weiterer API-call
This commit is contained in:
1 parent
bd63935a1e
commit
bf7119f580
15 files changed
+158
-101
No files matched your search
@@ -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,
|
||||||
|
}
|
||||||
Reference in new issue
Block a user