Add repositories
- Make model classes imutable - Move model classes to common module - Add repositories for backend and frontend - Backend repositories communicates with sqlite - Frontend repositories commuincates with rest api - Add rest api - Authentication via cookies - Add coroutines to forntend - Add change listener to repositories - Transmit change events to frontend repositories via websocket - Switch to server side sessions - Move setup from ui to cli
This commit is contained in:
parent
4e5dc610a3
commit
b7d6476a70
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
||||||
.idea/
|
.idea/
|
||||||
build/
|
build/
|
||||||
web/
|
web/
|
||||||
|
.sessions/
|
||||||
|
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
|
|
@ -35,7 +35,6 @@ kotlin {
|
||||||
compilations.all {
|
compilations.all {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
freeCompilerArgs += [
|
freeCompilerArgs += [
|
||||||
"-Xuse-experimental=io.ktor.locations.KtorExperimentalLocationsAPI",
|
|
||||||
"-Xuse-experimental=io.ktor.util.KtorExperimentalAPI"
|
"-Xuse-experimental=io.ktor.util.KtorExperimentalAPI"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -54,6 +53,7 @@ kotlin {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('stdlib-common')
|
implementation kotlin('stdlib-common')
|
||||||
|
implementation "de.westermann:KObserve-metadata:0.9.1"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,9 @@ kotlin {
|
||||||
|
|
||||||
implementation "io.ktor:ktor-server-netty:$ktor_version"
|
implementation "io.ktor:ktor-server-netty:$ktor_version"
|
||||||
implementation "io.ktor:ktor-auth:$ktor_version"
|
implementation "io.ktor:ktor-auth:$ktor_version"
|
||||||
implementation "io.ktor:ktor-locations:$ktor_version"
|
implementation "io.ktor:ktor-server-sessions:$ktor_version"
|
||||||
implementation "io.ktor:ktor-websockets:$ktor_version"
|
implementation "io.ktor:ktor-websockets:$ktor_version"
|
||||||
|
implementation "io.ktor:ktor-jackson:$ktor_version"
|
||||||
implementation 'org.jetbrains:kotlin-css-jvm:1.0.0-pre.70-kotlin-1.3.21'
|
implementation 'org.jetbrains:kotlin-css-jvm:1.0.0-pre.70-kotlin-1.3.21'
|
||||||
|
|
||||||
implementation "io.ktor:ktor-html-builder:$ktor_version"
|
implementation "io.ktor:ktor-html-builder:$ktor_version"
|
||||||
|
@ -83,6 +84,8 @@ kotlin {
|
||||||
|
|
||||||
implementation 'org.mindrot:jbcrypt:0.4'
|
implementation 'org.mindrot:jbcrypt:0.4'
|
||||||
|
|
||||||
|
implementation "de.westermann:KObserve-jvm:0.9.1"
|
||||||
|
|
||||||
api 'io.github.microutils:kotlin-logging:1.6.23'
|
api 'io.github.microutils:kotlin-logging:1.6.23'
|
||||||
api 'ch.qos.logback:logback-classic:1.2.3'
|
api 'ch.qos.logback:logback-classic:1.2.3'
|
||||||
api 'org.fusesource.jansi:jansi:1.8'
|
api 'org.fusesource.jansi:jansi:1.8'
|
||||||
|
@ -155,10 +158,12 @@ task run(type: JavaExec, dependsOn: [jvmMainClasses, jsJar]) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
args = []
|
args = []
|
||||||
|
standardInput = System.in
|
||||||
}
|
}
|
||||||
|
|
||||||
clean.doFirst {
|
clean.doFirst {
|
||||||
delete webFolder
|
delete webFolder
|
||||||
|
delete ".sessions"
|
||||||
}
|
}
|
||||||
|
|
||||||
task jar(type: ShadowJar, dependsOn: [jvmMainClasses, jsMainClasses, sass]) {
|
task jar(type: ShadowJar, dependsOn: [jvmMainClasses, jsMainClasses, sass]) {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
package kif.common.model
|
package de.kif.common
|
||||||
|
|
||||||
const val CALENDAR_GRID_WIDTH = 15
|
const val CALENDAR_GRID_WIDTH = 15
|
35
src/commonMain/kotlin/de/kif/common/Message.kt
Normal file
35
src/commonMain/kotlin/de/kif/common/Message.kt
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package de.kif.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Message(
|
||||||
|
val type: MessageType,
|
||||||
|
val repository: RepositoryType,
|
||||||
|
val id: Long
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun stringify(): String {
|
||||||
|
return json.stringify(serializer(), this)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val jsonContext = SerializersModule {}
|
||||||
|
|
||||||
|
val json = Json(context = jsonContext)
|
||||||
|
|
||||||
|
fun parse(data: String): Message {
|
||||||
|
return json.parse(serializer(), data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class MessageType {
|
||||||
|
CREATE, UPDATE, DELETE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class RepositoryType {
|
||||||
|
ROOM, SCHEDULE, TRACK, USER, WORK_GROUP
|
||||||
|
}
|
22
src/commonMain/kotlin/de/kif/common/Repository.kt
Normal file
22
src/commonMain/kotlin/de/kif/common/Repository.kt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package de.kif.common
|
||||||
|
|
||||||
|
import de.kif.common.model.Model
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
|
||||||
|
|
||||||
|
interface Repository<T : Model> {
|
||||||
|
|
||||||
|
suspend fun get(id: Long): T?
|
||||||
|
|
||||||
|
suspend fun create(model: T): Long
|
||||||
|
|
||||||
|
suspend fun update(model: T)
|
||||||
|
|
||||||
|
suspend fun delete(id: Long)
|
||||||
|
|
||||||
|
suspend fun all(): List<T>
|
||||||
|
|
||||||
|
val onCreate: EventHandler<Long>
|
||||||
|
val onUpdate: EventHandler<Long>
|
||||||
|
val onDelete: EventHandler<Long>
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package kif.common.model
|
package de.kif.common.model
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Color(
|
data class Color(
|
||||||
|
@ -21,25 +20,15 @@ data class Color(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transient
|
fun calcRedDouble() = red / 255.0
|
||||||
val redDouble
|
|
||||||
get() = red / 255.0
|
|
||||||
|
|
||||||
@Transient
|
fun calcGreenDouble() = green / 255.0
|
||||||
val greenDouble
|
|
||||||
get() = green / 255.0
|
|
||||||
|
|
||||||
@Transient
|
fun calcBlueDouble() = blue / 255.0
|
||||||
val blueDouble
|
|
||||||
get() = blue / 255.0
|
|
||||||
|
|
||||||
@Transient
|
fun calcLuminance(): Double = 0.2126 * calcRedDouble() + 0.7152 * calcGreenDouble() + 0.0722 * calcBlueDouble()
|
||||||
val luminance: Double
|
|
||||||
get() = 0.2126 * redDouble + 0.7152 * greenDouble + 0.0722 * blueDouble
|
|
||||||
|
|
||||||
@Transient
|
fun calcTextColor(): Color = if (calcLuminance() < 0.7) WHITE else BLACK
|
||||||
val textColor: Color
|
|
||||||
get() = if (luminance < 0.7) WHITE else BLACK
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(color: String): Color {
|
fun parse(color: String): Color {
|
||||||
|
@ -94,3 +83,5 @@ data class Color(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.parseColor() = Color.parse(this)
|
6
src/commonMain/kotlin/de/kif/common/model/Language.kt
Normal file
6
src/commonMain/kotlin/de/kif/common/model/Language.kt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package de.kif.common.model
|
||||||
|
|
||||||
|
enum class Language(val code: String, val localeName: String) {
|
||||||
|
GERMAN("DE", "Deutsch"),
|
||||||
|
ENGLISH("EN", "English")
|
||||||
|
}
|
3
src/commonMain/kotlin/de/kif/common/model/Model.kt
Normal file
3
src/commonMain/kotlin/de/kif/common/model/Model.kt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package de.kif.common.model
|
||||||
|
|
||||||
|
interface Model
|
|
@ -1,4 +1,4 @@
|
||||||
package de.kif.backend.model
|
package de.kif.common.model
|
||||||
|
|
||||||
enum class Permission {
|
enum class Permission {
|
||||||
USER, SCHEDULE, WORK_GROUP, ROOM, PERSON, ADMIN
|
USER, SCHEDULE, WORK_GROUP, ROOM, PERSON, ADMIN
|
11
src/commonMain/kotlin/de/kif/common/model/Room.kt
Normal file
11
src/commonMain/kotlin/de/kif/common/model/Room.kt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Room(
|
||||||
|
val id: Long? = null,
|
||||||
|
val name: String,
|
||||||
|
val places: Int,
|
||||||
|
val projector: Boolean
|
||||||
|
) : Model
|
12
src/commonMain/kotlin/de/kif/common/model/Schedule.kt
Normal file
12
src/commonMain/kotlin/de/kif/common/model/Schedule.kt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Schedule(
|
||||||
|
val id: Long?,
|
||||||
|
val workGroup: WorkGroup,
|
||||||
|
val room: Room,
|
||||||
|
val day: Int,
|
||||||
|
val time: Int
|
||||||
|
) : Model
|
10
src/commonMain/kotlin/de/kif/common/model/Track.kt
Normal file
10
src/commonMain/kotlin/de/kif/common/model/Track.kt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Track(
|
||||||
|
val id: Long?,
|
||||||
|
var name: String,
|
||||||
|
var color: Color
|
||||||
|
) : Model
|
16
src/commonMain/kotlin/de/kif/common/model/User.kt
Normal file
16
src/commonMain/kotlin/de/kif/common/model/User.kt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class User(
|
||||||
|
val id: Long?,
|
||||||
|
val username: String,
|
||||||
|
val password: String,
|
||||||
|
val permissions: Set<Permission>
|
||||||
|
) : Model {
|
||||||
|
|
||||||
|
fun checkPermission(permission: Permission): Boolean {
|
||||||
|
return permission in permissions || Permission.ADMIN in permissions
|
||||||
|
}
|
||||||
|
}
|
15
src/commonMain/kotlin/de/kif/common/model/WorkGroup.kt
Normal file
15
src/commonMain/kotlin/de/kif/common/model/WorkGroup.kt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package de.kif.common.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class WorkGroup(
|
||||||
|
val id: Long?,
|
||||||
|
val name: String,
|
||||||
|
val interested: Int,
|
||||||
|
val track: Track?,
|
||||||
|
val projector: Boolean,
|
||||||
|
val resolution: Boolean,
|
||||||
|
val length: Int,
|
||||||
|
val language: Language
|
||||||
|
) : Model
|
|
@ -1,58 +0,0 @@
|
||||||
package kif.common.model
|
|
||||||
|
|
||||||
import kotlinx.serialization.Polymorphic
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Message(
|
|
||||||
@Polymorphic
|
|
||||||
val message: MessageType
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun stringify(): String = Companion.stringify(this)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private val module = SerializersModule {
|
|
||||||
polymorphic(MessageType::class) {
|
|
||||||
MessageCreateCalendarEntry::class with MessageCreateCalendarEntry.serializer()
|
|
||||||
MessageDeleteCalendarEntry::class with MessageDeleteCalendarEntry.serializer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val json = Json(context = module)
|
|
||||||
|
|
||||||
fun stringify(message: Message): String {
|
|
||||||
return json.stringify(serializer(), message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parse(message: String): Message {
|
|
||||||
return json.parse(serializer(), message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class MessageType()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MessageCreateCalendarEntry(
|
|
||||||
val day: Int,
|
|
||||||
val time: Int,
|
|
||||||
val cellTime: Int,
|
|
||||||
val room: Int,
|
|
||||||
val workGroupId: Int,
|
|
||||||
val workGroupName: String,
|
|
||||||
val workGroupLength: Int,
|
|
||||||
val workGroupLanguage: String,
|
|
||||||
val workGroupColor: Color? = null
|
|
||||||
) : MessageType()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MessageDeleteCalendarEntry(
|
|
||||||
val day: Int,
|
|
||||||
val time: Int,
|
|
||||||
val roomId: Int,
|
|
||||||
val workGroupId: Int
|
|
||||||
) : MessageType()
|
|
76
src/jsMain/kotlin/de/kif/frontend/WebSocketClient.kt
Normal file
76
src/jsMain/kotlin/de/kif/frontend/WebSocketClient.kt
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package de.kif.frontend
|
||||||
|
|
||||||
|
import de.kif.common.Message
|
||||||
|
import de.kif.common.MessageType
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.frontend.repository.*
|
||||||
|
import de.westermann.kwebview.async
|
||||||
|
import org.w3c.dom.MessageEvent
|
||||||
|
import org.w3c.dom.WebSocket
|
||||||
|
import org.w3c.dom.events.Event
|
||||||
|
import kotlin.browser.window
|
||||||
|
|
||||||
|
class WebSocketClient() {
|
||||||
|
private val url = "ws://${window.location.host}/"
|
||||||
|
|
||||||
|
private lateinit var ws: WebSocket
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun onOpen(event: Event) {
|
||||||
|
console.log("Connected!")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMessage(messageEvent: MessageEvent) {
|
||||||
|
val message = Message.parse(messageEvent.data?.toString() ?: "")
|
||||||
|
|
||||||
|
for (handler in messageHandlers) {
|
||||||
|
if (handler.repository == message.repository) {
|
||||||
|
when (message.type) {
|
||||||
|
MessageType.CREATE -> handler.onCreate(message.id)
|
||||||
|
MessageType.UPDATE -> handler.onUpdate(message.id)
|
||||||
|
MessageType.DELETE -> handler.onDelete(message.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun onClose(event: Event) {
|
||||||
|
console.log("Disconnected!")
|
||||||
|
async(1000) {
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun onError(event: Event) {
|
||||||
|
console.log("An error occurred!")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connect() {
|
||||||
|
ws = WebSocket(url)
|
||||||
|
|
||||||
|
ws.onopen = this::onOpen
|
||||||
|
ws.onmessage = this::onMessage
|
||||||
|
ws.onclose = this::onClose
|
||||||
|
ws.onerror = this::onError
|
||||||
|
}
|
||||||
|
|
||||||
|
private val messageHandlers: List<MessageHandler> = listOf(
|
||||||
|
RoomRepository.handler,
|
||||||
|
ScheduleRepository.handler,
|
||||||
|
TrackRepository.handler,
|
||||||
|
UserRepository.handler,
|
||||||
|
WorkGroupRepository.handler
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class MessageHandler(val repository: RepositoryType) {
|
||||||
|
abstract fun onCreate(id: Long)
|
||||||
|
abstract fun onUpdate(id: Long)
|
||||||
|
abstract fun onDelete(id: Long)
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
package de.kif.frontend.calendar
|
|
||||||
|
|
||||||
import de.westermann.kwebview.View
|
|
||||||
import de.westermann.kwebview.ViewCollection
|
|
||||||
import de.westermann.kwebview.createHtmlView
|
|
||||||
|
|
||||||
class Calendar : ViewCollection<View>(createHtmlView()) {
|
|
||||||
init {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
39
src/jsMain/kotlin/de/kif/frontend/extensions.kt
Normal file
39
src/jsMain/kotlin/de/kif/frontend/extensions.kt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package de.kif.frontend
|
||||||
|
|
||||||
|
import org.w3c.dom.*
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
import kotlin.coroutines.startCoroutine
|
||||||
|
|
||||||
|
|
||||||
|
operator fun HTMLCollection.iterator() = object : Iterator<HTMLElement> {
|
||||||
|
private var index = 0
|
||||||
|
override fun hasNext(): Boolean {
|
||||||
|
return index < this@iterator.length
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun next(): HTMLElement {
|
||||||
|
return this@iterator.get(index++) as HTMLElement
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun NodeList.iterator() = object : Iterator<Node> {
|
||||||
|
private var index = 0
|
||||||
|
override fun hasNext(): Boolean {
|
||||||
|
return index < this@iterator.length
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun next(): Node {
|
||||||
|
return this@iterator.get(index++)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launch(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> Unit) =
|
||||||
|
block.startCoroutine(Continuation(context) { result ->
|
||||||
|
result.onFailure { exception ->
|
||||||
|
console.error(exception)
|
||||||
|
}
|
||||||
|
})
|
|
@ -1,381 +1,13 @@
|
||||||
package de.kif.frontend
|
package de.kif.frontend
|
||||||
|
|
||||||
import de.westermann.kobserve.property.mapBinding
|
import de.kif.frontend.views.initCalendar
|
||||||
import de.westermann.kwebview.*
|
|
||||||
import de.westermann.kwebview.components.Body
|
|
||||||
import de.westermann.kwebview.components.init
|
import de.westermann.kwebview.components.init
|
||||||
import kif.common.model.*
|
|
||||||
import org.w3c.dom.*
|
|
||||||
import org.w3c.dom.events.EventListener
|
|
||||||
import org.w3c.dom.events.MouseEvent
|
|
||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
import kotlin.browser.window
|
|
||||||
import kotlin.collections.Iterator
|
|
||||||
import kotlin.collections.List
|
|
||||||
import kotlin.collections.emptyList
|
|
||||||
import kotlin.collections.filter
|
|
||||||
import kotlin.collections.find
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
import kotlin.collections.listOf
|
|
||||||
import kotlin.collections.minus
|
|
||||||
import kotlin.collections.plus
|
|
||||||
import kotlin.dom.appendText
|
|
||||||
|
|
||||||
class CalendarTools(entry: CalendarEntry, view: HTMLElement) : View(view) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
var linkM10: HTMLAnchorElement? = null
|
|
||||||
var linkM5: HTMLAnchorElement? = null
|
|
||||||
var linkReset: HTMLAnchorElement? = null
|
|
||||||
var linkP5: HTMLAnchorElement? = null
|
|
||||||
var linkP10: HTMLAnchorElement? = null
|
|
||||||
var linkDel: HTMLAnchorElement? = null
|
|
||||||
|
|
||||||
for (element in html.children) {
|
|
||||||
when {
|
|
||||||
element.classList.contains("calendar-tools-m10") -> linkM10 = element as? HTMLAnchorElement
|
|
||||||
element.classList.contains("calendar-tools-m5") -> linkM5 = element as? HTMLAnchorElement
|
|
||||||
element.classList.contains("calendar-tools-reset") -> linkReset = element as? HTMLAnchorElement
|
|
||||||
element.classList.contains("calendar-tools-p5") -> linkP5 = element as? HTMLAnchorElement
|
|
||||||
element.classList.contains("calendar-tools-p10") -> linkP10 = element as? HTMLAnchorElement
|
|
||||||
element.classList.contains("calendar-tools-del") -> linkDel = element as? HTMLAnchorElement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
linkM10 = linkM10 ?: run {
|
|
||||||
val link = createHtmlView<HTMLAnchorElement>()
|
|
||||||
link.classList.add("calendar-tools-m10")
|
|
||||||
link.textContent = "-10"
|
|
||||||
html.appendChild(link)
|
|
||||||
link
|
|
||||||
}
|
|
||||||
linkM10.removeAttribute("href")
|
|
||||||
linkM10.addEventListener("click", EventListener {
|
|
||||||
classList += "pending"
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.timeId - 10}/${entry.workGroup}") {
|
|
||||||
println("success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
linkM5 = linkM5 ?: run {
|
|
||||||
val link = createHtmlView<HTMLAnchorElement>()
|
|
||||||
link.classList.add("calendar-tools-m5")
|
|
||||||
link.textContent = "-5"
|
|
||||||
html.appendChild(link)
|
|
||||||
link
|
|
||||||
}
|
|
||||||
linkM5.removeAttribute("href")
|
|
||||||
linkM5.addEventListener("click", EventListener {
|
|
||||||
classList += "pending"
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.timeId - 5}/${entry.workGroup}") {
|
|
||||||
println("success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
linkReset = linkReset ?: run {
|
|
||||||
val link = createHtmlView<HTMLAnchorElement>()
|
|
||||||
link.classList.add("calendar-tools-reset")
|
|
||||||
link.textContent = "reset"
|
|
||||||
html.appendChild(link)
|
|
||||||
link
|
|
||||||
}
|
|
||||||
linkReset.removeAttribute("href")
|
|
||||||
linkReset.addEventListener("click", EventListener {
|
|
||||||
classList += "pending"
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.cellTime}/${entry.workGroup}") {
|
|
||||||
println("success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
linkP5 = linkP5 ?: run {
|
|
||||||
val link = createHtmlView<HTMLAnchorElement>()
|
|
||||||
link.classList.add("calendar-tools-p5")
|
|
||||||
link.textContent = "+5"
|
|
||||||
html.appendChild(link)
|
|
||||||
link
|
|
||||||
}
|
|
||||||
linkP5.removeAttribute("href")
|
|
||||||
linkP5.addEventListener("click", EventListener {
|
|
||||||
classList += "pending"
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.timeId + 5}/${entry.workGroup}") {
|
|
||||||
println("success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
linkP10 = linkP10 ?: run {
|
|
||||||
val link = createHtmlView<HTMLAnchorElement>()
|
|
||||||
link.classList.add("calendar-tools-p10")
|
|
||||||
link.textContent = "+10"
|
|
||||||
html.appendChild(link)
|
|
||||||
link
|
|
||||||
}
|
|
||||||
linkP10.removeAttribute("href")
|
|
||||||
linkP10.addEventListener("click", EventListener {
|
|
||||||
classList += "pending"
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.timeId + 10}/${entry.workGroup}") {
|
|
||||||
println("success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
linkDel = linkDel ?: run {
|
|
||||||
val link = createHtmlView<HTMLAnchorElement>()
|
|
||||||
link.classList.add("calendar-tools-del")
|
|
||||||
link.textContent = "del"
|
|
||||||
html.appendChild(link)
|
|
||||||
link
|
|
||||||
}
|
|
||||||
linkDel.removeAttribute("href")
|
|
||||||
linkDel.addEventListener("click", EventListener {
|
|
||||||
classList += "pending"
|
|
||||||
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
|
|
||||||
println("success")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CalendarEntry(view: HTMLElement) : View(view) {
|
|
||||||
|
|
||||||
private lateinit var mouseDelta: Point
|
|
||||||
private var newCell: CalendarCell? = null
|
|
||||||
|
|
||||||
|
|
||||||
var day by dataset.property("day")
|
|
||||||
val dayId by dataset.property("day").mapBinding { it?.toIntOrNull() ?: 0 }
|
|
||||||
|
|
||||||
var time by dataset.property("time")
|
|
||||||
val timeId by dataset.property("time").mapBinding { it?.toIntOrNull() ?: 0 }
|
|
||||||
|
|
||||||
var room by dataset.property("room")
|
|
||||||
val roomId by dataset.property("room").mapBinding { it?.toIntOrNull() ?: 0 }
|
|
||||||
|
|
||||||
var cellTime by dataset.property("cellTime")
|
|
||||||
var language by dataset.property("language")
|
|
||||||
|
|
||||||
var workGroup by dataset.property("workgroup")
|
|
||||||
val workGroupId by dataset.property("workgroup").mapBinding { it?.toIntOrNull() ?: 0 }
|
|
||||||
private fun onMove(event: MouseEvent) {
|
|
||||||
val position = event.toPoint() - mouseDelta
|
|
||||||
|
|
||||||
newCell?.classList?.remove("drag")
|
|
||||||
|
|
||||||
val cell = calendarCells.find {
|
|
||||||
position in it.dimension
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cell != null) {
|
|
||||||
cell.classList.add("drag")
|
|
||||||
cell += this
|
|
||||||
newCell = cell
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onFinishMove(event: MouseEvent) {
|
|
||||||
classList -= "drag"
|
|
||||||
|
|
||||||
newCell?.let { cell ->
|
|
||||||
cell.classList -= "drag"
|
|
||||||
|
|
||||||
val newTime = cell.time
|
|
||||||
val newRoom = cell.room
|
|
||||||
val day =
|
|
||||||
(document.getElementsByClassName("calendar")[0] as? HTMLElement)?.dataset?.get("day")?.toIntOrNull()
|
|
||||||
?: 0
|
|
||||||
|
|
||||||
classList += "pending"
|
|
||||||
get("/calendar/$day/$room/$time/-1") {
|
|
||||||
get("/calendar/$day/$newRoom/$newTime/$workGroup") {
|
|
||||||
println("success")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newCell = null
|
|
||||||
|
|
||||||
for (it in listeners) {
|
|
||||||
it.detach()
|
|
||||||
}
|
|
||||||
listeners = emptyList()
|
|
||||||
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var listeners: List<de.westermann.kobserve.event.EventListener<*>> = emptyList()
|
|
||||||
|
|
||||||
init {
|
|
||||||
onMouseDown { event ->
|
|
||||||
if (event.target != html || "pending" in classList) {
|
|
||||||
event.stopPropagation()
|
|
||||||
return@onMouseDown
|
|
||||||
}
|
|
||||||
|
|
||||||
classList += "drag"
|
|
||||||
|
|
||||||
mouseDelta = event.toPoint() - point
|
|
||||||
|
|
||||||
listeners = listOf(
|
|
||||||
Body.onMouseMove.reference(this::onMove),
|
|
||||||
Body.onMouseUp.reference(this::onFinishMove),
|
|
||||||
Body.onMouseLeave.reference(this::onFinishMove)
|
|
||||||
)
|
|
||||||
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
var calendarTools: CalendarTools? = null
|
|
||||||
for (item in html.children) {
|
|
||||||
if (item.classList.contains("calendar-tools")) {
|
|
||||||
calendarTools = CalendarTools(this, item)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (calendarTools == null) {
|
|
||||||
calendarTools = CalendarTools(this, createHtmlView())
|
|
||||||
html.appendChild(calendarTools.html)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun create(
|
|
||||||
day: Int,
|
|
||||||
time: Int,
|
|
||||||
cellTime: Int,
|
|
||||||
room: Int,
|
|
||||||
workGroupId: Int,
|
|
||||||
workGroupName: String,
|
|
||||||
workGroupLength: Int,
|
|
||||||
workGroupLanguage: String,
|
|
||||||
workGroupColor: Color?
|
|
||||||
): CalendarEntry {
|
|
||||||
val entry = CalendarEntry(createHtmlView())
|
|
||||||
|
|
||||||
entry.day = day.toString()
|
|
||||||
entry.time = time.toString()
|
|
||||||
entry.cellTime = cellTime.toString()
|
|
||||||
entry.room = room.toString()
|
|
||||||
entry.workGroup = workGroupId.toString()
|
|
||||||
entry.language = workGroupLanguage
|
|
||||||
if (workGroupColor != null) {
|
|
||||||
|
|
||||||
entry.style {
|
|
||||||
val size = workGroupLength / CALENDAR_GRID_WIDTH.toDouble()
|
|
||||||
val pos = (time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()
|
|
||||||
|
|
||||||
val pct = "${pos * 100}%"
|
|
||||||
val sz = "${size * 100}%"
|
|
||||||
|
|
||||||
left = pct
|
|
||||||
top = "calc($pct + 0.1rem)"
|
|
||||||
|
|
||||||
width = sz
|
|
||||||
height = "calc($sz - 0.2rem)"
|
|
||||||
|
|
||||||
backgroundColor = workGroupColor.toString()
|
|
||||||
color = workGroupColor.textColor.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry.html.appendText(workGroupName)
|
|
||||||
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CalendarCell(view: HTMLElement) : ViewCollection<CalendarEntry>(view) {
|
|
||||||
var day by dataset.property("day")
|
|
||||||
val dayId by dataset.property("day").mapBinding { it?.toIntOrNull() ?: 0 }
|
|
||||||
|
|
||||||
var time by dataset.property("time")
|
|
||||||
val timeId by dataset.property("time").mapBinding { it?.toIntOrNull() ?: 0 }
|
|
||||||
|
|
||||||
var room by dataset.property("room")
|
|
||||||
val roomId by dataset.property("room").mapBinding { it?.toIntOrNull() ?: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
var calendarEntries: List<CalendarEntry> = emptyList()
|
|
||||||
var calendarCells: List<CalendarCell> = emptyList()
|
|
||||||
|
|
||||||
fun main() = init {
|
fun main() = init {
|
||||||
|
WebSocketClient()
|
||||||
|
|
||||||
val ws = WebSocket("ws://${window.location.host}/".also { println(it) })
|
if (document.getElementsByClassName("calendar").length > 0) {
|
||||||
|
initCalendar()
|
||||||
ws.onmessage = {
|
|
||||||
val messageWrapper = Message.parse(it.data?.toString() ?: "")
|
|
||||||
val message = messageWrapper.message
|
|
||||||
|
|
||||||
println(message)
|
|
||||||
|
|
||||||
when (message) {
|
|
||||||
is MessageCreateCalendarEntry -> {
|
|
||||||
val entry = CalendarEntry.create(
|
|
||||||
message.day,
|
|
||||||
message.time,
|
|
||||||
message.cellTime,
|
|
||||||
message.room,
|
|
||||||
message.workGroupId,
|
|
||||||
message.workGroupName,
|
|
||||||
message.workGroupLength,
|
|
||||||
message.workGroupLanguage,
|
|
||||||
message.workGroupColor
|
|
||||||
)
|
|
||||||
for (cell in calendarCells) {
|
|
||||||
if (cell.dayId == message.day && cell.timeId == message.cellTime && cell.roomId == message.room) {
|
|
||||||
cell.html.appendChild(entry.html)
|
|
||||||
calendarEntries += entry
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
is MessageDeleteCalendarEntry -> {
|
|
||||||
val remove = calendarEntries.filter { entry ->
|
|
||||||
entry.dayId == message.day &&
|
|
||||||
entry.timeId == message.time &&
|
|
||||||
entry.roomId == message.roomId &&
|
|
||||||
entry.workGroupId == message.workGroupId
|
|
||||||
}
|
|
||||||
calendarEntries -= remove
|
|
||||||
remove.forEach {
|
|
||||||
it.html.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.onopen = {
|
|
||||||
console.log("yes!")
|
|
||||||
}
|
|
||||||
|
|
||||||
calendarEntries = document.getElementsByClassName("calendar-entry")
|
|
||||||
.iterator().asSequence().map(::CalendarEntry).toList()
|
|
||||||
|
|
||||||
calendarCells = document.getElementsByClassName("calendar-cell")
|
|
||||||
.iterator().asSequence().filter { it.dataset["time"] != null }.map(::CalendarCell).toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun HTMLCollection.iterator() = object : Iterator<HTMLElement> {
|
|
||||||
private var index = 0
|
|
||||||
override fun hasNext(): Boolean {
|
|
||||||
return index < this@iterator.length
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun next(): HTMLElement {
|
|
||||||
return this@iterator.get(index++) as HTMLElement
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
106
src/jsMain/kotlin/de/kif/frontend/repository/Ajax.kt
Normal file
106
src/jsMain/kotlin/de/kif/frontend/repository/Ajax.kt
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package de.kif.frontend.repository
|
||||||
|
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.model.Model
|
||||||
|
import org.w3c.xhr.XMLHttpRequest
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import kotlin.js.Promise
|
||||||
|
|
||||||
|
suspend fun repositoryGet(
|
||||||
|
url: String
|
||||||
|
): dynamic {
|
||||||
|
console.log("GET: $url")
|
||||||
|
val promise = Promise<dynamic> { resolve, reject ->
|
||||||
|
val xhttp = XMLHttpRequest()
|
||||||
|
|
||||||
|
xhttp.onreadystatechange = {
|
||||||
|
if (xhttp.readyState == 4.toShort()) {
|
||||||
|
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
|
||||||
|
resolve(JSON.parse(xhttp.responseText))
|
||||||
|
} else {
|
||||||
|
reject(NoSuchElementException("${xhttp.status}: ${xhttp.statusText}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhttp.open("GET", url, true)
|
||||||
|
|
||||||
|
xhttp.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val d = promise.await()
|
||||||
|
|
||||||
|
return if (d.OK) {
|
||||||
|
d.data
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: NoSuchElementException) {
|
||||||
|
console.error(e)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun repositoryPost(
|
||||||
|
url: String,
|
||||||
|
data: String? = null
|
||||||
|
): dynamic {
|
||||||
|
console.log("POST: $url", data)
|
||||||
|
val promise = Promise<dynamic> { resolve, reject ->
|
||||||
|
val xhttp = XMLHttpRequest()
|
||||||
|
|
||||||
|
xhttp.onreadystatechange = {
|
||||||
|
if (xhttp.readyState == 4.toShort()) {
|
||||||
|
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
|
||||||
|
resolve(JSON.parse(xhttp.responseText))
|
||||||
|
} else {
|
||||||
|
reject(NoSuchElementException("${xhttp.status}: ${xhttp.statusText}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhttp.open("POST", url, true)
|
||||||
|
xhttp.setRequestHeader("Content-type", "application/json");
|
||||||
|
|
||||||
|
xhttp.send(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val d = promise.await()
|
||||||
|
|
||||||
|
return if (d.OK) {
|
||||||
|
d.data
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: NoSuchElementException) {
|
||||||
|
console.error(e)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
|
||||||
|
then({ cont.resume(it) }, { cont.resumeWithException(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
class RepositoryDelegate<T : Model>(
|
||||||
|
private val repository: Repository<T>,
|
||||||
|
private val id: Long
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var backing: T? = null
|
||||||
|
|
||||||
|
suspend fun get(): T {
|
||||||
|
if (backing == null) {
|
||||||
|
backing = repository.get(id) ?: throw NoSuchElementException()
|
||||||
|
}
|
||||||
|
return backing!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun set(value: T) {
|
||||||
|
backing = value
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package de.kif.frontend.repository
|
||||||
|
|
||||||
|
import de.kif.common.Message
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.common.model.Room
|
||||||
|
import de.kif.frontend.MessageHandler
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.serialization.DynamicObjectParser
|
||||||
|
import kotlinx.serialization.list
|
||||||
|
|
||||||
|
object RoomRepository : Repository<Room> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private val parser = DynamicObjectParser()
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): Room? {
|
||||||
|
val json = repositoryGet("/api/room/$id") ?: return null
|
||||||
|
return parser.parse(json, Room.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: Room): Long {
|
||||||
|
return repositoryPost("/api/rooms", Message.json.stringify(Room.serializer(), model))?.toLong()
|
||||||
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: Room) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
repositoryPost("/api/room/${model.id}", Message.json.stringify(Room.serializer(), model))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
repositoryPost("/api/room/$id/delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<Room> {
|
||||||
|
val json = repositoryGet("/api/rooms") ?: return emptyList()
|
||||||
|
return parser.parse(json, Room.serializer().list)
|
||||||
|
}
|
||||||
|
|
||||||
|
val handler = object : MessageHandler(RepositoryType.ROOM) {
|
||||||
|
|
||||||
|
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||||
|
|
||||||
|
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||||
|
|
||||||
|
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package de.kif.frontend.repository
|
||||||
|
|
||||||
|
import de.kif.common.Message
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.common.model.Schedule
|
||||||
|
import de.kif.frontend.MessageHandler
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.serialization.DynamicObjectParser
|
||||||
|
import kotlinx.serialization.list
|
||||||
|
|
||||||
|
object ScheduleRepository : Repository<Schedule> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private val parser = DynamicObjectParser()
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): Schedule? {
|
||||||
|
val json = repositoryGet("/api/schedule/$id") ?: return null
|
||||||
|
return parser.parse(json, Schedule.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: Schedule): Long {
|
||||||
|
return repositoryPost("/api/schedules", Message.json.stringify(Schedule.serializer(), model))?.toLong()
|
||||||
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: Schedule) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
repositoryPost("/api/schedule/${model.id}", Message.json.stringify(Schedule.serializer(), model))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
repositoryPost("/api/schedule/$id/delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<Schedule> {
|
||||||
|
val json = repositoryGet("/api/schedules") ?: return emptyList()
|
||||||
|
return parser.parse(json, Schedule.serializer().list)
|
||||||
|
}
|
||||||
|
|
||||||
|
val handler = object : MessageHandler(RepositoryType.SCHEDULE) {
|
||||||
|
|
||||||
|
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||||
|
|
||||||
|
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||||
|
|
||||||
|
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package de.kif.frontend.repository
|
||||||
|
|
||||||
|
import de.kif.common.Message
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.common.model.Track
|
||||||
|
import de.kif.frontend.MessageHandler
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.serialization.DynamicObjectParser
|
||||||
|
import kotlinx.serialization.list
|
||||||
|
|
||||||
|
object TrackRepository : Repository<Track> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private val parser = DynamicObjectParser()
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): Track? {
|
||||||
|
val json = repositoryGet("/api/track/$id") ?: return null
|
||||||
|
return parser.parse(json, Track.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: Track): Long {
|
||||||
|
return repositoryPost("/api/tracks", Message.json.stringify(Track.serializer(), model))?.toLong()
|
||||||
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: Track) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
repositoryPost("/api/track/${model.id}", Message.json.stringify(Track.serializer(), model))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
repositoryPost("/api/track/$id/delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<Track> {
|
||||||
|
val json = repositoryGet("/api/tracks") ?: return emptyList()
|
||||||
|
return parser.parse(json, Track.serializer().list)
|
||||||
|
}
|
||||||
|
|
||||||
|
val handler = object : MessageHandler(RepositoryType.TRACK) {
|
||||||
|
|
||||||
|
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||||
|
|
||||||
|
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||||
|
|
||||||
|
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package de.kif.frontend.repository
|
||||||
|
|
||||||
|
import de.kif.common.Message
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.common.model.User
|
||||||
|
import de.kif.frontend.MessageHandler
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.serialization.DynamicObjectParser
|
||||||
|
import kotlinx.serialization.list
|
||||||
|
|
||||||
|
object UserRepository : Repository<User> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private val parser = DynamicObjectParser()
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): User? {
|
||||||
|
val json = repositoryGet("/api/user/$id") ?: return null
|
||||||
|
return parser.parse(json, User.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: User): Long {
|
||||||
|
return repositoryPost("/api/users", Message.json.stringify(User.serializer(), model))?.toLong()
|
||||||
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: User) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
repositoryPost("/api/user/${model.id}", Message.json.stringify(User.serializer(), model))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
repositoryPost("/api/user/$id/delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<User> {
|
||||||
|
val json = repositoryGet("/api/users") ?: return emptyList()
|
||||||
|
return parser.parse(json, User.serializer().list)
|
||||||
|
}
|
||||||
|
|
||||||
|
val handler = object : MessageHandler(RepositoryType.USER) {
|
||||||
|
|
||||||
|
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||||
|
|
||||||
|
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||||
|
|
||||||
|
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package de.kif.frontend.repository
|
||||||
|
|
||||||
|
import de.kif.common.Message
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.common.model.WorkGroup
|
||||||
|
import de.kif.frontend.MessageHandler
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.serialization.DynamicObjectParser
|
||||||
|
import kotlinx.serialization.list
|
||||||
|
|
||||||
|
object WorkGroupRepository : Repository<WorkGroup> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private val parser = DynamicObjectParser()
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): WorkGroup? {
|
||||||
|
val json = repositoryGet("/api/workgroup/$id") ?: return null
|
||||||
|
return parser.parse(json, WorkGroup.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: WorkGroup): Long {
|
||||||
|
return repositoryPost("/api/workgroups", Message.json.stringify(WorkGroup.serializer(), model))?.toLong()
|
||||||
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: WorkGroup) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
repositoryPost("/api/workgroup/${model.id}", Message.json.stringify(WorkGroup.serializer(), model))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
repositoryPost("/api/workgroup/$id/delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<WorkGroup> {
|
||||||
|
val json = repositoryGet("/api/workgroups") ?: return emptyList()
|
||||||
|
return parser.parse(json, WorkGroup.serializer().list)
|
||||||
|
}
|
||||||
|
|
||||||
|
val handler = object : MessageHandler(RepositoryType.WORK_GROUP) {
|
||||||
|
|
||||||
|
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||||
|
|
||||||
|
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||||
|
|
||||||
|
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||||
|
}
|
||||||
|
}
|
359
src/jsMain/kotlin/de/kif/frontend/views/Calendar.kt
Normal file
359
src/jsMain/kotlin/de/kif/frontend/views/Calendar.kt
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
package de.kif.frontend.views
|
||||||
|
|
||||||
|
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||||
|
import de.kif.common.model.Room
|
||||||
|
import de.kif.common.model.Schedule
|
||||||
|
import de.kif.frontend.iterator
|
||||||
|
import de.kif.frontend.launch
|
||||||
|
import de.kif.frontend.repository.RepositoryDelegate
|
||||||
|
import de.kif.frontend.repository.RoomRepository
|
||||||
|
import de.kif.frontend.repository.ScheduleRepository
|
||||||
|
import de.westermann.kwebview.*
|
||||||
|
import de.westermann.kwebview.components.Body
|
||||||
|
import org.w3c.dom.HTMLAnchorElement
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.events.EventListener
|
||||||
|
import org.w3c.dom.events.MouseEvent
|
||||||
|
import org.w3c.dom.get
|
||||||
|
import kotlin.browser.document
|
||||||
|
import kotlin.dom.appendText
|
||||||
|
import kotlin.dom.isText
|
||||||
|
|
||||||
|
class CalendarTools(entry: CalendarEntry, view: HTMLElement) : View(view) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
var linkM10: HTMLAnchorElement? = null
|
||||||
|
var linkM5: HTMLAnchorElement? = null
|
||||||
|
var linkReset: HTMLAnchorElement? = null
|
||||||
|
var linkP5: HTMLAnchorElement? = null
|
||||||
|
var linkP10: HTMLAnchorElement? = null
|
||||||
|
var linkDel: HTMLAnchorElement? = null
|
||||||
|
|
||||||
|
for (element in html.children) {
|
||||||
|
when {
|
||||||
|
element.classList.contains("calendar-tools-m10") -> linkM10 = element as? HTMLAnchorElement
|
||||||
|
element.classList.contains("calendar-tools-m5") -> linkM5 = element as? HTMLAnchorElement
|
||||||
|
element.classList.contains("calendar-tools-reset") -> linkReset = element as? HTMLAnchorElement
|
||||||
|
element.classList.contains("calendar-tools-p5") -> linkP5 = element as? HTMLAnchorElement
|
||||||
|
element.classList.contains("calendar-tools-p10") -> linkP10 = element as? HTMLAnchorElement
|
||||||
|
element.classList.contains("calendar-tools-del") -> linkDel = element as? HTMLAnchorElement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
linkM10 = linkM10 ?: run {
|
||||||
|
val link = createHtmlView<HTMLAnchorElement>()
|
||||||
|
link.classList.add("calendar-tools-m10")
|
||||||
|
link.textContent = "-10"
|
||||||
|
html.appendChild(link)
|
||||||
|
link
|
||||||
|
}
|
||||||
|
linkM10.removeAttribute("href")
|
||||||
|
linkM10.addEventListener("click", EventListener {
|
||||||
|
entry.pending = true
|
||||||
|
launch {
|
||||||
|
val s = entry.schedule.get()
|
||||||
|
ScheduleRepository.update(s.copy(time = s.time - 10))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
linkM5 = linkM5 ?: run {
|
||||||
|
val link = createHtmlView<HTMLAnchorElement>()
|
||||||
|
link.classList.add("calendar-tools-m5")
|
||||||
|
link.textContent = "-5"
|
||||||
|
html.appendChild(link)
|
||||||
|
link
|
||||||
|
}
|
||||||
|
linkM5.removeAttribute("href")
|
||||||
|
linkM5.addEventListener("click", EventListener {
|
||||||
|
entry.pending = true
|
||||||
|
launch {
|
||||||
|
val s = entry.schedule.get()
|
||||||
|
ScheduleRepository.update(s.copy(time = s.time - 5))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
linkReset = linkReset ?: run {
|
||||||
|
val link = createHtmlView<HTMLAnchorElement>()
|
||||||
|
link.classList.add("calendar-tools-reset")
|
||||||
|
link.textContent = "reset"
|
||||||
|
html.appendChild(link)
|
||||||
|
link
|
||||||
|
}
|
||||||
|
linkReset.removeAttribute("href")
|
||||||
|
linkReset.addEventListener("click", EventListener {
|
||||||
|
entry.pending = true
|
||||||
|
launch {
|
||||||
|
val s = entry.schedule.get()
|
||||||
|
ScheduleRepository.update(s.copy(time = s.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
linkP5 = linkP5 ?: run {
|
||||||
|
val link = createHtmlView<HTMLAnchorElement>()
|
||||||
|
link.classList.add("calendar-tools-p5")
|
||||||
|
link.textContent = "+5"
|
||||||
|
html.appendChild(link)
|
||||||
|
link
|
||||||
|
}
|
||||||
|
linkP5.removeAttribute("href")
|
||||||
|
linkP5.addEventListener("click", EventListener {
|
||||||
|
entry.pending = true
|
||||||
|
launch {
|
||||||
|
val s = entry.schedule.get()
|
||||||
|
ScheduleRepository.update(s.copy(time = s.time + 5))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
linkP10 = linkP10 ?: run {
|
||||||
|
val link = createHtmlView<HTMLAnchorElement>()
|
||||||
|
link.classList.add("calendar-tools-p10")
|
||||||
|
link.textContent = "+10"
|
||||||
|
html.appendChild(link)
|
||||||
|
link
|
||||||
|
}
|
||||||
|
linkP10.removeAttribute("href")
|
||||||
|
linkP10.addEventListener("click", EventListener {
|
||||||
|
entry.pending = true
|
||||||
|
launch {
|
||||||
|
val s = entry.schedule.get()
|
||||||
|
ScheduleRepository.update(s.copy(time = s.time + 10))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
linkDel = linkDel ?: run {
|
||||||
|
val link = createHtmlView<HTMLAnchorElement>()
|
||||||
|
link.classList.add("calendar-tools-del")
|
||||||
|
link.textContent = "del"
|
||||||
|
html.appendChild(link)
|
||||||
|
link
|
||||||
|
}
|
||||||
|
linkDel.removeAttribute("href")
|
||||||
|
linkDel.addEventListener("click", EventListener {
|
||||||
|
entry.pending = true
|
||||||
|
launch {
|
||||||
|
ScheduleRepository.delete(entry.scheduleId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CalendarEntry(view: HTMLElement) : View(view) {
|
||||||
|
|
||||||
|
private lateinit var mouseDelta: Point
|
||||||
|
private var newCell: CalendarCell? = null
|
||||||
|
|
||||||
|
private var language by dataset.property("language")
|
||||||
|
|
||||||
|
val scheduleId = dataset["id"]?.toLongOrNull() ?: 0
|
||||||
|
|
||||||
|
val schedule = RepositoryDelegate(ScheduleRepository, scheduleId)
|
||||||
|
|
||||||
|
var pending by classList.property("pending")
|
||||||
|
|
||||||
|
private fun onMove(event: MouseEvent) {
|
||||||
|
val position = event.toPoint() - mouseDelta
|
||||||
|
|
||||||
|
val cell = calendarCells.find {
|
||||||
|
position in it.dimension
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (cell != null) {
|
||||||
|
cell += this
|
||||||
|
|
||||||
|
if (newCell == null) {
|
||||||
|
style {
|
||||||
|
left = "0"
|
||||||
|
top = "0.1rem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newCell = cell
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onFinishMove(event: MouseEvent) {
|
||||||
|
classList -= "drag"
|
||||||
|
|
||||||
|
newCell?.let { cell ->
|
||||||
|
launch {
|
||||||
|
val newTime = cell.time
|
||||||
|
val newRoom = cell.getRoom()
|
||||||
|
|
||||||
|
pending = true
|
||||||
|
|
||||||
|
val s = schedule.get().copy(room = newRoom, time = newTime)
|
||||||
|
|
||||||
|
ScheduleRepository.update(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newCell = null
|
||||||
|
|
||||||
|
for (it in listeners) {
|
||||||
|
it.detach()
|
||||||
|
}
|
||||||
|
listeners = emptyList()
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var listeners: List<de.westermann.kobserve.event.EventListener<*>> = emptyList()
|
||||||
|
|
||||||
|
init {
|
||||||
|
onMouseDown { event ->
|
||||||
|
if (event.target != html || "pending" in classList) {
|
||||||
|
event.stopPropagation()
|
||||||
|
return@onMouseDown
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
classList += "drag"
|
||||||
|
|
||||||
|
val s = schedule.get()
|
||||||
|
val time = s.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
|
||||||
|
|
||||||
|
val p = calendarCells.find {
|
||||||
|
it.day == s.day && it.time == time && it.roomId == s.room.id
|
||||||
|
}?.dimension?.center ?: dimension.center
|
||||||
|
|
||||||
|
mouseDelta = event.toPoint() - p
|
||||||
|
|
||||||
|
listeners = listOf(
|
||||||
|
Body.onMouseMove.reference(this::onMove),
|
||||||
|
Body.onMouseUp.reference(this::onFinishMove),
|
||||||
|
Body.onMouseLeave.reference(this::onFinishMove)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
var calendarTools: CalendarTools? = null
|
||||||
|
for (item in html.children) {
|
||||||
|
if (item.classList.contains("calendar-tools")) {
|
||||||
|
calendarTools = CalendarTools(this, item)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (calendarTools == null) {
|
||||||
|
calendarTools = CalendarTools(this, createHtmlView())
|
||||||
|
html.appendChild(calendarTools.html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(schedule: Schedule) {
|
||||||
|
pending = false
|
||||||
|
|
||||||
|
language = schedule.workGroup.language.code
|
||||||
|
|
||||||
|
this.schedule.set(schedule)
|
||||||
|
|
||||||
|
style {
|
||||||
|
val size = schedule.workGroup.length / CALENDAR_GRID_WIDTH.toDouble()
|
||||||
|
val pos = (schedule.time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()
|
||||||
|
|
||||||
|
val ps = "${pos * 100}%"
|
||||||
|
val sz = "${size * 100}%"
|
||||||
|
|
||||||
|
left = ps
|
||||||
|
top = "calc($ps + 0.1rem)"
|
||||||
|
|
||||||
|
width = sz
|
||||||
|
height = "calc($sz - 0.2rem)"
|
||||||
|
|
||||||
|
if (schedule.workGroup.track?.color != null) {
|
||||||
|
backgroundColor = schedule.workGroup.track.color.toString()
|
||||||
|
color = schedule.workGroup.track.color.calcTextColor().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (element in html.childNodes) {
|
||||||
|
if (element.isText) {
|
||||||
|
html.removeChild(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.appendText(schedule.workGroup.name)
|
||||||
|
|
||||||
|
|
||||||
|
val time = schedule.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH
|
||||||
|
val cell = calendarCells.find {
|
||||||
|
it.day == schedule.day && it.time == time && it.roomId == schedule.room.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell != null && cell.html != html.parentElement) {
|
||||||
|
cell += this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(schedule: Schedule): CalendarEntry {
|
||||||
|
val entry = CalendarEntry(createHtmlView())
|
||||||
|
|
||||||
|
entry.load(schedule)
|
||||||
|
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CalendarCell(view: HTMLElement) : ViewCollection<CalendarEntry>(view) {
|
||||||
|
val day = dataset["day"]?.toIntOrNull() ?: 0
|
||||||
|
val time = dataset["time"]?.toIntOrNull() ?: 0
|
||||||
|
val roomId = dataset["room"]?.toLongOrNull() ?: 0
|
||||||
|
|
||||||
|
private lateinit var room: Room
|
||||||
|
|
||||||
|
suspend fun getRoom(): Room {
|
||||||
|
if (this::room.isInitialized) {
|
||||||
|
return room
|
||||||
|
}
|
||||||
|
|
||||||
|
room = RoomRepository.get(roomId) ?: throw NoSuchElementException()
|
||||||
|
return room
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var calendarEntries: List<CalendarEntry> = emptyList()
|
||||||
|
var calendarCells: List<CalendarCell> = emptyList()
|
||||||
|
|
||||||
|
fun initCalendar() {
|
||||||
|
calendarEntries = document.getElementsByClassName("calendar-entry")
|
||||||
|
.iterator().asSequence().map(::CalendarEntry).toList()
|
||||||
|
|
||||||
|
calendarCells = document.getElementsByClassName("calendar-cell")
|
||||||
|
.iterator().asSequence().filter { it.dataset["time"] != null }.map(::CalendarCell).toList()
|
||||||
|
|
||||||
|
ScheduleRepository.onCreate {
|
||||||
|
launch {
|
||||||
|
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
|
||||||
|
CalendarEntry.create(schedule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScheduleRepository.onUpdate {
|
||||||
|
launch {
|
||||||
|
val schedule = ScheduleRepository.get(it) ?: throw NoSuchElementException()
|
||||||
|
var found = false
|
||||||
|
for (entry in calendarEntries) {
|
||||||
|
if (entry.scheduleId == it) {
|
||||||
|
entry.load(schedule)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
CalendarEntry.create(schedule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScheduleRepository.onDelete {
|
||||||
|
for (entry in calendarEntries) {
|
||||||
|
if (entry.scheduleId == it) {
|
||||||
|
entry.html.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package de.westermann.kwebview
|
||||||
import de.westermann.kobserve.event.EventListener
|
import de.westermann.kobserve.event.EventListener
|
||||||
import de.westermann.kobserve.Property
|
import de.westermann.kobserve.Property
|
||||||
import de.westermann.kobserve.ReadOnlyProperty
|
import de.westermann.kobserve.ReadOnlyProperty
|
||||||
|
import de.westermann.kobserve.property.property
|
||||||
import org.w3c.dom.DOMTokenList
|
import org.w3c.dom.DOMTokenList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,6 +98,23 @@ class ClassList(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun property(clazz: String): Property<Boolean> {
|
||||||
|
if (clazz in bound) {
|
||||||
|
throw IllegalArgumentException("Class is already bound!")
|
||||||
|
}
|
||||||
|
|
||||||
|
val property = property(get(clazz))
|
||||||
|
|
||||||
|
bound[clazz] = Bound(property,
|
||||||
|
property.onChange.reference {
|
||||||
|
list.toggle(clazz, property.value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return property
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun unbind(clazz: String) {
|
fun unbind(clazz: String) {
|
||||||
if (clazz !in bound) {
|
if (clazz !in bound) {
|
||||||
throw IllegalArgumentException("Class is not bound!")
|
throw IllegalArgumentException("Class is not bound!")
|
||||||
|
|
|
@ -27,6 +27,9 @@ data class Dimension(
|
||||||
val bottom: Double
|
val bottom: Double
|
||||||
get() = top + height
|
get() = top + height
|
||||||
|
|
||||||
|
val center: Point
|
||||||
|
get() = Point(left + width / 2.0, top + height / 2.0)
|
||||||
|
|
||||||
val edges: Set<Point>
|
val edges: Set<Point>
|
||||||
get() = setOf(
|
get() = setOf(
|
||||||
Point(left, top),
|
Point(left, top),
|
||||||
|
|
|
@ -110,7 +110,7 @@ fun get(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun post(
|
fun postForm(
|
||||||
url: String,
|
url: String,
|
||||||
data: Map<String, String> = emptyMap(),
|
data: Map<String, String> = emptyMap(),
|
||||||
onError: (Int) -> Unit = {},
|
onError: (Int) -> Unit = {},
|
||||||
|
@ -141,3 +141,43 @@ fun post(
|
||||||
xhttp.send()
|
xhttp.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun postJson(
|
||||||
|
url: String,
|
||||||
|
data: dynamic,
|
||||||
|
onError: (Int) -> Unit = {},
|
||||||
|
onSuccess: (String) -> Unit = {}
|
||||||
|
) {
|
||||||
|
val xhttp = XMLHttpRequest()
|
||||||
|
|
||||||
|
xhttp.onreadystatechange = {
|
||||||
|
if (xhttp.readyState == 4.toShort()) {
|
||||||
|
console.log(xhttp.status)
|
||||||
|
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
|
||||||
|
onSuccess(xhttp.responseText)
|
||||||
|
} else {
|
||||||
|
onError(xhttp.status.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhttp.open("POST", url, true)
|
||||||
|
|
||||||
|
if (data.isNotEmpty()) {
|
||||||
|
xhttp.setRequestHeader("Content-type", "application/json");
|
||||||
|
xhttp.send(data)
|
||||||
|
} else {
|
||||||
|
xhttp.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun jsonObject(block: (dynamic) -> Unit): dynamic {
|
||||||
|
val json = js("{}")
|
||||||
|
block(json)
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
fun jsonArray(block: (dynamic) -> Unit): dynamic {
|
||||||
|
val json = js("[]")
|
||||||
|
block(json)
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
|
@ -1,190 +1,40 @@
|
||||||
package de.kif.backend
|
package de.kif.backend
|
||||||
|
|
||||||
import de.kif.backend.database.Connection
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
import de.kif.backend.model.User
|
|
||||||
import de.kif.backend.route.*
|
import de.kif.backend.route.*
|
||||||
|
import de.kif.backend.route.api.*
|
||||||
|
import de.kif.backend.util.pushService
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.ApplicationCall
|
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
import io.ktor.application.log
|
|
||||||
import io.ktor.auth.Authentication
|
|
||||||
import io.ktor.auth.FormAuthChallenge
|
|
||||||
import io.ktor.auth.UserPasswordCredential
|
|
||||||
import io.ktor.auth.form
|
|
||||||
import io.ktor.features.*
|
import io.ktor.features.*
|
||||||
import io.ktor.http.content.files
|
import io.ktor.http.content.files
|
||||||
import io.ktor.http.content.static
|
import io.ktor.http.content.static
|
||||||
import io.ktor.locations.Location
|
import io.ktor.jackson.jackson
|
||||||
import io.ktor.locations.Locations
|
|
||||||
import io.ktor.response.respondRedirect
|
|
||||||
import io.ktor.routing.routing
|
import io.ktor.routing.routing
|
||||||
import io.ktor.sessions.*
|
|
||||||
import io.ktor.util.hex
|
|
||||||
import io.ktor.websocket.WebSockets
|
import io.ktor.websocket.WebSockets
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
data class PortalSession(val id: Int, val name: String) {
|
|
||||||
suspend fun getUser(call: ApplicationCall): User {
|
|
||||||
val user = User.find(name)
|
|
||||||
if (user == null || user.id != id) {
|
|
||||||
call.sessions.clear<PortalSession>()
|
|
||||||
call.respondRedirect("/login?error")
|
|
||||||
throw IllegalAccessException()
|
|
||||||
}
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/")
|
|
||||||
class LocationDashboard()
|
|
||||||
|
|
||||||
@Location("/calendar/{day}")
|
|
||||||
data class LocationCalendar(val day: Int) {
|
|
||||||
@Location("/{room}/{time}")
|
|
||||||
data class LocationCalendarEdit(
|
|
||||||
val calendar: LocationCalendar,
|
|
||||||
val room: Int,
|
|
||||||
val time: Int,
|
|
||||||
val search: String = ""
|
|
||||||
) {
|
|
||||||
val day: Int get() = calendar.day
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/{room}/{time}/{workGroup}")
|
|
||||||
data class LocationCalendarSet(
|
|
||||||
val calendar: LocationCalendar,
|
|
||||||
val room: Int,
|
|
||||||
val time: Int,
|
|
||||||
val workGroup: Int,
|
|
||||||
val next: String? = null
|
|
||||||
) {
|
|
||||||
val day: Int get() = calendar.day
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/time-to-room")
|
|
||||||
data class LocationCalendarTimeToRoom(val calendar: LocationCalendar){
|
|
||||||
val day: Int get() = calendar.day
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/room-to-time")
|
|
||||||
data class LocationCalendarRoomToTime(val calendar: LocationCalendar){
|
|
||||||
val day: Int get() = calendar.day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/login")
|
|
||||||
data class LocationLogin(val username: String = "", val password: String = "", val next: String = "/")
|
|
||||||
|
|
||||||
@Location("/logout")
|
|
||||||
class LocationLogout()
|
|
||||||
|
|
||||||
@Location("/account")
|
|
||||||
class LocationAccount()
|
|
||||||
|
|
||||||
@Location("/user")
|
|
||||||
data class LocationUser(val search: String = "") {
|
|
||||||
@Location("/{id}")
|
|
||||||
data class Edit(val id: Int)
|
|
||||||
|
|
||||||
@Location("/new")
|
|
||||||
class New()
|
|
||||||
|
|
||||||
@Location("/{id}/delete")
|
|
||||||
data class Delete(val id: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/workgroup")
|
|
||||||
data class LocationWorkGroup(val search: String = "") {
|
|
||||||
@Location("/{id}")
|
|
||||||
data class Edit(val id: Int)
|
|
||||||
|
|
||||||
@Location("/new")
|
|
||||||
class New()
|
|
||||||
|
|
||||||
@Location("/{id}/delete")
|
|
||||||
data class Delete(val id: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/track")
|
|
||||||
data class LocationTrack(val search: String = "") {
|
|
||||||
@Location("/{id}")
|
|
||||||
data class Edit(val id: Int)
|
|
||||||
|
|
||||||
@Location("/new")
|
|
||||||
class New()
|
|
||||||
|
|
||||||
@Location("/{id}/delete")
|
|
||||||
data class Delete(val id: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/room")
|
|
||||||
data class LocationRoom(val search: String = "") {
|
|
||||||
@Location("/{id}")
|
|
||||||
data class Edit(val id: Int)
|
|
||||||
|
|
||||||
@Location("/new")
|
|
||||||
class New()
|
|
||||||
|
|
||||||
@Location("/{id}/delete")
|
|
||||||
data class Delete(val id: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Location("/person")
|
|
||||||
data class LocationPerson(val search: String = "") {
|
|
||||||
@Location("/{id}")
|
|
||||||
data class Edit(val id: Int)
|
|
||||||
|
|
||||||
@Location("/new")
|
|
||||||
class New()
|
|
||||||
|
|
||||||
@Location("/{id}/delete")
|
|
||||||
data class Delete(val id: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Application.main() {
|
fun Application.main() {
|
||||||
Connection.init()
|
|
||||||
|
|
||||||
install(DefaultHeaders)
|
install(DefaultHeaders)
|
||||||
install(CallLogging)
|
install(CallLogging)
|
||||||
install(ConditionalHeaders)
|
install(ConditionalHeaders)
|
||||||
install(Compression)
|
install(Compression)
|
||||||
install(DataConversion)
|
install(DataConversion)
|
||||||
install(Locations)
|
|
||||||
install(WebSockets)
|
install(WebSockets)
|
||||||
|
|
||||||
install(Authentication) {
|
install(ContentNegotiation) {
|
||||||
form {
|
jackson {
|
||||||
userParamName = LocationLogin::username.name
|
enable(SerializationFeature.INDENT_OUTPUT) // Pretty Prints the JSON
|
||||||
passwordParamName = LocationLogin::password.name
|
|
||||||
challenge = FormAuthChallenge.Redirect { _ ->
|
|
||||||
"/login?error"
|
|
||||||
}
|
|
||||||
validate { credential: UserPasswordCredential ->
|
|
||||||
val user = User.find(credential.name) ?: return@validate null
|
|
||||||
if (user.checkPassword(credential.password)) user else null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val sessionKey = hex("1234567890abcdef") //TODO
|
security()
|
||||||
install(Sessions) {
|
|
||||||
cookie<PortalSession>("SESSION") {
|
|
||||||
transform(SessionTransportTransformerMessageAuthentication(sessionKey))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
routing {
|
routing {
|
||||||
static("/static") {
|
static("/static") {
|
||||||
files(Resources.directory)
|
files(Resources.directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
// UI routes
|
||||||
val firstStart = User.exists()
|
|
||||||
if (firstStart) {
|
|
||||||
log.info("Please create the first user and restart the server!")
|
|
||||||
setup()
|
|
||||||
} else {
|
|
||||||
dashboard()
|
dashboard()
|
||||||
calendar()
|
calendar()
|
||||||
login()
|
login()
|
||||||
|
@ -193,11 +43,18 @@ fun Application.main() {
|
||||||
workGroup()
|
workGroup()
|
||||||
track()
|
track()
|
||||||
room()
|
room()
|
||||||
person()
|
|
||||||
user()
|
user()
|
||||||
|
|
||||||
|
// RESTful routes
|
||||||
|
authenticateApi()
|
||||||
|
|
||||||
|
roomApi()
|
||||||
|
scheduleApi()
|
||||||
|
trackApi()
|
||||||
|
userApi()
|
||||||
|
workGroupApi()
|
||||||
|
|
||||||
|
// Web socket push notifications
|
||||||
pushService()
|
pushService()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,13 +1,50 @@
|
||||||
package de.kif.backend
|
package de.kif.backend
|
||||||
|
|
||||||
|
import de.kif.backend.database.Connection
|
||||||
|
import de.kif.backend.repository.UserRepository
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.User
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
object Main {
|
object Main {
|
||||||
@Suppress("UnusedMainParameter")
|
@Suppress("UnusedMainParameter")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
Connection.init()
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
if (UserRepository.all().isEmpty()) {
|
||||||
|
|
||||||
|
println("Please create a root user")
|
||||||
|
|
||||||
|
var username: String? = null
|
||||||
|
while (username == null) {
|
||||||
|
print("Username: ")
|
||||||
|
username = readLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
var password: String? = null
|
||||||
|
while (password == null) {
|
||||||
|
print("Password: ")
|
||||||
|
password = System.console()?.readPassword()?.toString() ?: readLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
UserRepository.create(
|
||||||
|
User(
|
||||||
|
null,
|
||||||
|
username,
|
||||||
|
hashPassword(password),
|
||||||
|
setOf(Permission.ADMIN)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
embeddedServer(
|
embeddedServer(
|
||||||
factory = Netty,
|
factory = Netty,
|
||||||
port = 8080,
|
port = 8080,
|
||||||
|
|
125
src/jvmMain/kotlin/de/kif/backend/Security.kt
Normal file
125
src/jvmMain/kotlin/de/kif/backend/Security.kt
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
package de.kif.backend
|
||||||
|
|
||||||
|
import de.kif.backend.repository.UserRepository
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.User
|
||||||
|
import io.ktor.application.Application
|
||||||
|
import io.ktor.application.ApplicationCall
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.auth.*
|
||||||
|
import io.ktor.request.path
|
||||||
|
import io.ktor.response.respondRedirect
|
||||||
|
import io.ktor.sessions.*
|
||||||
|
import io.ktor.util.hex
|
||||||
|
import io.ktor.util.pipeline.PipelineContext
|
||||||
|
import org.mindrot.jbcrypt.BCrypt
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface ErrorContext {
|
||||||
|
suspend infix fun onFailure(block: suspend () -> Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ErrorContextIgnore : ErrorContext {
|
||||||
|
override suspend fun onFailure(block: suspend () -> Unit) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorContextOccur() : ErrorContext {
|
||||||
|
override suspend fun onFailure(block: suspend () -> Unit) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun PipelineContext<Unit, ApplicationCall>.authenticate(
|
||||||
|
vararg permissions: Permission,
|
||||||
|
block: (user: User) -> Unit
|
||||||
|
): ErrorContext {
|
||||||
|
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||||
|
return if (user == null || permissions.any { !user.checkPermission(it) }) {
|
||||||
|
ErrorContextOccur()
|
||||||
|
} else {
|
||||||
|
block(user)
|
||||||
|
ErrorContextIgnore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun PipelineContext<Unit, ApplicationCall>.authenticateOrRedirect(
|
||||||
|
vararg permissions: Permission,
|
||||||
|
block: (user: User) -> Unit
|
||||||
|
) {
|
||||||
|
authenticate(*permissions, block = block) onFailure {
|
||||||
|
call.respondRedirect("/login?redirect=${call.request.path()}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun PipelineContext<Unit, ApplicationCall>.isAuthenticated(vararg permissions: Permission): User? {
|
||||||
|
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||||
|
return if (user == null || permissions.any { !user.checkPermission(it) }) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class PortalSession(val userId: Long) {
|
||||||
|
suspend fun getUser(call: ApplicationCall): User {
|
||||||
|
val user = UserRepository.get(userId)
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
call.sessions.clear<PortalSession>()
|
||||||
|
call.respondRedirect("/login?onFailure")
|
||||||
|
throw IllegalAccessException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class UserPrinciple(val user: User) : Principal
|
||||||
|
|
||||||
|
fun checkPassword(password: String, hash: String): Boolean {
|
||||||
|
return BCrypt.checkpw(password, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hashPassword(password: String): String {
|
||||||
|
return BCrypt.hashpw(password, BCrypt.gensalt())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Application.security() {
|
||||||
|
|
||||||
|
install(Authentication) {
|
||||||
|
form {
|
||||||
|
userParamName = "username"
|
||||||
|
passwordParamName = "password"
|
||||||
|
challenge = FormAuthChallenge.Redirect { _ ->
|
||||||
|
"/login?onFailure"
|
||||||
|
}
|
||||||
|
validate { credential: UserPasswordCredential ->
|
||||||
|
val user = UserRepository.find(credential.name) ?: return@validate null
|
||||||
|
if (checkPassword(credential.password, user.password)) UserPrinciple(user) else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val encryptionKey =
|
||||||
|
hex("80 51 b8 13 b4 73 a9 69 c7 b0 10 ad 08 06 11 e3".replace(" ", ""))
|
||||||
|
val signKey =
|
||||||
|
hex(
|
||||||
|
"d1 20 23 8c 01 f8 f0 0d 9d 7c ff 68 21 97 75 31 38 3f fb 91 20 3a 8d 86 d4 e9 d8 50 f8 71 f1 dc".replace(
|
||||||
|
" ",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
install(Sessions) {
|
||||||
|
cookie<PortalSession>(
|
||||||
|
"SESSION",
|
||||||
|
directorySessionStorage(File(".sessions"), cached = false)
|
||||||
|
) {
|
||||||
|
cookie.path = "/"
|
||||||
|
transform(SessionTransportTransformerMessageAuthentication(signKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,9 +16,7 @@ object Connection {
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
SchemaUtils.create(
|
SchemaUtils.create(
|
||||||
DbPerson, DbPersonConstraint,
|
DbTrack, DbWorkGroup,
|
||||||
DbTrack, DbWorkGroup, DbWorkGroupConstraint,
|
|
||||||
DbLeader, DbWorkGroupOrder,
|
|
||||||
DbRoom, DbSchedule,
|
DbRoom, DbSchedule,
|
||||||
DbUser, DbUserPermission
|
DbUser, DbUserPermission
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,71 +1,30 @@
|
||||||
package de.kif.backend.database
|
package de.kif.backend.database
|
||||||
|
|
||||||
import de.kif.backend.model.Permission
|
import de.kif.common.model.Language
|
||||||
|
import de.kif.common.model.Permission
|
||||||
import org.jetbrains.exposed.sql.Table
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
object DbPerson : Table() {
|
|
||||||
val id = integer("id").autoIncrement().primaryKey()
|
|
||||||
val firstName = varchar("first_name", 64)
|
|
||||||
val lastName = varchar("last_name", 64)
|
|
||||||
|
|
||||||
val arrival = long("arrival").nullable()
|
|
||||||
val departure = long("departure").nullable()
|
|
||||||
}
|
|
||||||
|
|
||||||
object DbPersonConstraint : Table() {
|
|
||||||
val id = integer("id").autoIncrement().primaryKey()
|
|
||||||
val personId = integer("person_id")
|
|
||||||
|
|
||||||
val type = enumeration("type", DbConstraintType::class)
|
|
||||||
val time = long("time")
|
|
||||||
val duration = integer("duration").default(0)
|
|
||||||
val day = integer("day")
|
|
||||||
}
|
|
||||||
|
|
||||||
object DbTrack : Table() {
|
object DbTrack : Table() {
|
||||||
val id = integer("id").autoIncrement().primaryKey()
|
val id = long("id").autoIncrement().primaryKey()
|
||||||
val name = varchar("name", 64)
|
val name = varchar("name", 64)
|
||||||
val color = varchar("color", 32)
|
val color = varchar("color", 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
object DbWorkGroup : Table() {
|
object DbWorkGroup : Table() {
|
||||||
val id = integer("id").autoIncrement().primaryKey()
|
val id = long("id").autoIncrement().primaryKey()
|
||||||
val name = varchar("first_name", 64)
|
val name = varchar("first_name", 64)
|
||||||
|
|
||||||
val interested = integer("interested")
|
val interested = integer("interested")
|
||||||
val trackId = integer("track_id").nullable()
|
val trackId = long("track_id").nullable()
|
||||||
val projector = bool("projector")
|
val projector = bool("projector")
|
||||||
val resolution = bool("resolution")
|
val resolution = bool("resolution")
|
||||||
val language = enumeration("language", Language::class)
|
val language = enumeration("language", Language::class)
|
||||||
|
|
||||||
val length = integer("length")
|
val length = integer("length")
|
||||||
|
|
||||||
val start = long("start").nullable()
|
|
||||||
val end = long("end").nullable()
|
|
||||||
}
|
|
||||||
|
|
||||||
object DbWorkGroupConstraint : Table() {
|
|
||||||
val id = integer("id").autoIncrement().primaryKey()
|
|
||||||
val workGroupId = integer("work_group_id")
|
|
||||||
|
|
||||||
val type = enumeration("type", DbConstraintType::class)
|
|
||||||
val time = long("time")
|
|
||||||
val duration = integer("duration").default(0)
|
|
||||||
val day = integer("day")
|
|
||||||
}
|
|
||||||
|
|
||||||
object DbLeader : Table() {
|
|
||||||
val workGroupId = integer("work_group_id").primaryKey(0)
|
|
||||||
val personId = integer("person_id").primaryKey(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
object DbWorkGroupOrder : Table() {
|
|
||||||
val beforeWorkGroupId = integer("before_work_group_id").primaryKey(0)
|
|
||||||
val afterWorkGroupId = integer("after_work_group_id").primaryKey(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object DbRoom : Table() {
|
object DbRoom : Table() {
|
||||||
val id = integer("id").autoIncrement().primaryKey()
|
val id = long("id").autoIncrement().primaryKey()
|
||||||
val name = varchar("name", 64)
|
val name = varchar("name", 64)
|
||||||
|
|
||||||
val places = integer("places")
|
val places = integer("places")
|
||||||
|
@ -73,30 +32,20 @@ object DbRoom : Table() {
|
||||||
}
|
}
|
||||||
|
|
||||||
object DbSchedule : Table() {
|
object DbSchedule : Table() {
|
||||||
val workGroupId = integer("work_group_id").primaryKey(0)
|
val id = long("id").autoIncrement().primaryKey()
|
||||||
val day = integer("day").primaryKey(1)
|
val workGroupId = long("work_group_id").index()
|
||||||
val time = integer("time_slot").primaryKey(2)
|
val roomId = long("room_id").index()
|
||||||
val roomId = integer("room_id").primaryKey(3)
|
val day = integer("day").index()
|
||||||
}
|
val time = integer("time_slot")
|
||||||
|
|
||||||
enum class DbConstraintType {
|
|
||||||
BEGIN, END, BLOCKED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object DbUser : Table() {
|
object DbUser : Table() {
|
||||||
val userId = integer("id").autoIncrement().primaryKey()
|
val id = long("id").autoIncrement().primaryKey()
|
||||||
val username = varchar("username", 64).uniqueIndex()
|
val username = varchar("username", 64).uniqueIndex()
|
||||||
val password = varchar("password", 64)
|
val password = varchar("password", 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
object DbUserPermission : Table() {
|
object DbUserPermission : Table() {
|
||||||
val userId = integer("id").primaryKey(0)
|
val userId = long("id").primaryKey(0)
|
||||||
val permission = enumeration("permission", Permission::class).primaryKey(1)
|
val permission = enumeration("permission", Permission::class).primaryKey(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Language(val value: String) {
|
|
||||||
GERMAN("Deutsch"), ENGLISH("English");
|
|
||||||
|
|
||||||
override fun toString() = value
|
|
||||||
val code = value.take(2).toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
package de.kif.backend.model
|
|
||||||
|
|
||||||
import de.kif.backend.database.DbPerson
|
|
||||||
import de.kif.backend.database.DbPersonConstraint
|
|
||||||
import de.kif.backend.database.dbQuery
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
|
|
||||||
class Person(
|
|
||||||
var id: Int = -1,
|
|
||||||
var firstName: String = "",
|
|
||||||
var lastName: String = "",
|
|
||||||
var arrival: Long? = null,
|
|
||||||
var departure: Long? = null
|
|
||||||
) {
|
|
||||||
var constraints: Set<PersonConstraint> = emptySet()
|
|
||||||
|
|
||||||
suspend fun save() {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbPerson.insert {
|
|
||||||
it[firstName] = this@Person.firstName
|
|
||||||
it[lastName] = this@Person.lastName
|
|
||||||
it[arrival] = this@Person.arrival
|
|
||||||
it[departure] = this@Person.departure
|
|
||||||
}[DbPerson.id]!!
|
|
||||||
this@Person.id = newId
|
|
||||||
}
|
|
||||||
for (constraint in constraints) {
|
|
||||||
constraint.save(this@Person.id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbPerson.update({ DbPerson.id eq id }) {
|
|
||||||
it[firstName] = this@Person.firstName
|
|
||||||
it[lastName] = this@Person.lastName
|
|
||||||
it[arrival] = this@Person.arrival
|
|
||||||
it[departure] = this@Person.departure
|
|
||||||
}
|
|
||||||
|
|
||||||
DbPersonConstraint.deleteWhere { DbPersonConstraint.personId eq id }
|
|
||||||
}
|
|
||||||
for (constraint in constraints) {
|
|
||||||
constraint.save(this@Person.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete() {
|
|
||||||
val id = id
|
|
||||||
if (id >= 0) {
|
|
||||||
dbQuery {
|
|
||||||
DbPersonConstraint.deleteWhere { DbPersonConstraint.personId eq id }
|
|
||||||
DbPerson.deleteWhere { DbPerson.id eq id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun loadConstraints() {
|
|
||||||
if (id >= 0) {
|
|
||||||
constraints = PersonConstraint.get(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
suspend fun get(personId: Int): Person? = dbQuery {
|
|
||||||
val result = DbPerson.select { DbPerson.id eq personId }.firstOrNull() ?: return@dbQuery null
|
|
||||||
Person(
|
|
||||||
result[DbPerson.id],
|
|
||||||
result[DbPerson.firstName],
|
|
||||||
result[DbPerson.lastName],
|
|
||||||
result[DbPerson.arrival],
|
|
||||||
result[DbPerson.departure]
|
|
||||||
)
|
|
||||||
}?.apply { loadConstraints() }
|
|
||||||
|
|
||||||
suspend fun list(): List<Person> = dbQuery {
|
|
||||||
val query = DbPerson.selectAll()
|
|
||||||
query.map { result ->
|
|
||||||
Person(
|
|
||||||
result[DbPerson.id],
|
|
||||||
result[DbPerson.firstName],
|
|
||||||
result[DbPerson.lastName],
|
|
||||||
result[DbPerson.arrival],
|
|
||||||
result[DbPerson.departure]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.onEach { it.loadConstraints() }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
package de.kif.backend.model
|
|
||||||
|
|
||||||
import de.kif.backend.database.DbConstraintType
|
|
||||||
import de.kif.backend.database.DbPersonConstraint
|
|
||||||
import de.kif.backend.database.dbQuery
|
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
|
||||||
import org.jetbrains.exposed.sql.insert
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.update
|
|
||||||
|
|
||||||
sealed class PersonConstraint(
|
|
||||||
var id: Int = -1
|
|
||||||
) {
|
|
||||||
|
|
||||||
abstract suspend fun save(personId: Int)
|
|
||||||
|
|
||||||
suspend fun delete() {
|
|
||||||
val id = id
|
|
||||||
if (id >= 0) {
|
|
||||||
dbQuery {
|
|
||||||
DbPersonConstraint.deleteWhere { DbPersonConstraint.id eq id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BeginOnDay(
|
|
||||||
var time: Long = 0,
|
|
||||||
var day: Int = 0
|
|
||||||
) : PersonConstraint() {
|
|
||||||
override suspend fun save(personId: Int) {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbPersonConstraint.insert {
|
|
||||||
it[this@insert.personId] = personId
|
|
||||||
it[type] = DbConstraintType.BEGIN
|
|
||||||
it[time] = this@BeginOnDay.time
|
|
||||||
it[day] = this@BeginOnDay.day
|
|
||||||
}[DbPersonConstraint.id]!!
|
|
||||||
this@BeginOnDay.id = newId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbPersonConstraint.update({ DbPersonConstraint.id eq id }) {
|
|
||||||
it[this@update.personId] = personId
|
|
||||||
it[type] = DbConstraintType.BEGIN
|
|
||||||
it[time] = this@BeginOnDay.time
|
|
||||||
it[day] = this@BeginOnDay.day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EndOnDay(
|
|
||||||
var time: Long = 0,
|
|
||||||
var day: Int = 0
|
|
||||||
) : PersonConstraint() {
|
|
||||||
override suspend fun save(personId: Int) {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbPersonConstraint.insert {
|
|
||||||
it[this@insert.personId] = personId
|
|
||||||
it[type] = DbConstraintType.END
|
|
||||||
it[time] = this@EndOnDay.time
|
|
||||||
it[day] = this@EndOnDay.day
|
|
||||||
}[DbPersonConstraint.id]!!
|
|
||||||
this@EndOnDay.id = newId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbPersonConstraint.update({ DbPersonConstraint.id eq id }) {
|
|
||||||
it[this@update.personId] = personId
|
|
||||||
it[type] = DbConstraintType.END
|
|
||||||
it[time] = this@EndOnDay.time
|
|
||||||
it[day] = this@EndOnDay.day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BlockedOnDay(
|
|
||||||
var time: Long = 0,
|
|
||||||
var duration: Int = 0,
|
|
||||||
var day: Int = 0
|
|
||||||
) : PersonConstraint() {
|
|
||||||
|
|
||||||
override suspend fun save(personId: Int) {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbPersonConstraint.insert {
|
|
||||||
it[this@insert.personId] = personId
|
|
||||||
it[type] = DbConstraintType.BLOCKED
|
|
||||||
it[time] = this@BlockedOnDay.time
|
|
||||||
it[duration] = this@BlockedOnDay.duration
|
|
||||||
it[day] = this@BlockedOnDay.day
|
|
||||||
}[DbPersonConstraint.id]!!
|
|
||||||
this@BlockedOnDay.id = newId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbPersonConstraint.update({ DbPersonConstraint.id eq id }) {
|
|
||||||
it[this@update.personId] = personId
|
|
||||||
it[type] = DbConstraintType.BLOCKED
|
|
||||||
it[time] = this@BlockedOnDay.time
|
|
||||||
it[duration] = this@BlockedOnDay.duration
|
|
||||||
it[day] = this@BlockedOnDay.day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
suspend fun get(personId: Int): Set<PersonConstraint> = dbQuery {
|
|
||||||
val result = DbPersonConstraint.select { DbPersonConstraint.personId eq personId }
|
|
||||||
result.map {
|
|
||||||
val id = it[DbPersonConstraint.id]
|
|
||||||
val type = it[DbPersonConstraint.type]
|
|
||||||
val time = it[DbPersonConstraint.time]
|
|
||||||
val duration = it[DbPersonConstraint.duration]
|
|
||||||
val day = it[DbPersonConstraint.day]
|
|
||||||
|
|
||||||
when (type) {
|
|
||||||
DbConstraintType.BEGIN -> PersonConstraint.BeginOnDay(time, day)
|
|
||||||
DbConstraintType.END -> PersonConstraint.EndOnDay(time, day)
|
|
||||||
DbConstraintType.BLOCKED -> PersonConstraint.BlockedOnDay(time, duration, day)
|
|
||||||
}.also { it.id = id }
|
|
||||||
}.toSet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
package de.kif.backend.model
|
|
||||||
|
|
||||||
import de.kif.backend.database.DbRoom
|
|
||||||
import de.kif.backend.database.dbQuery
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
|
|
||||||
class Room(
|
|
||||||
var id: Int = -1,
|
|
||||||
var name: String = "",
|
|
||||||
var places: Int = 0,
|
|
||||||
var projector: Boolean = false
|
|
||||||
) {
|
|
||||||
suspend fun save() {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbRoom.insert {
|
|
||||||
it[name] = this@Room.name
|
|
||||||
it[places] = this@Room.places
|
|
||||||
it[projector] = this@Room.projector
|
|
||||||
}[DbRoom.id]!!
|
|
||||||
this@Room.id = newId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbRoom.update({ DbRoom.id eq id }) {
|
|
||||||
it[name] = this@Room.name
|
|
||||||
it[places] = this@Room.places
|
|
||||||
it[projector] = this@Room.projector
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete() {
|
|
||||||
val id = id
|
|
||||||
if (id >= 0) {
|
|
||||||
for (it in Schedule.getByRoom(id)) {
|
|
||||||
it.delete()
|
|
||||||
}
|
|
||||||
dbQuery {
|
|
||||||
DbRoom.deleteWhere { DbRoom.id eq id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Room
|
|
||||||
|
|
||||||
if (id != other.id) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
suspend fun get(roomId: Int): Room = dbQuery {
|
|
||||||
val result = DbRoom.select { DbRoom.id eq roomId }.firstOrNull() ?: throw IllegalArgumentException()
|
|
||||||
Room(
|
|
||||||
result[DbRoom.id],
|
|
||||||
result[DbRoom.name],
|
|
||||||
result[DbRoom.places],
|
|
||||||
result[DbRoom.projector]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun list(): List<Room> = dbQuery {
|
|
||||||
val query = DbRoom.selectAll()
|
|
||||||
query.map { result ->
|
|
||||||
Room(
|
|
||||||
result[DbRoom.id],
|
|
||||||
result[DbRoom.name],
|
|
||||||
result[DbRoom.places],
|
|
||||||
result[DbRoom.projector]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
package de.kif.backend.model
|
|
||||||
|
|
||||||
import de.kif.backend.database.DbSchedule
|
|
||||||
import de.kif.backend.database.dbQuery
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
|
|
||||||
data class Schedule(
|
|
||||||
val workGroupId: Int,
|
|
||||||
val day: Int,
|
|
||||||
val time: Int,
|
|
||||||
val roomId: Int
|
|
||||||
) {
|
|
||||||
lateinit var workGroup: WorkGroup
|
|
||||||
lateinit var room: Room
|
|
||||||
|
|
||||||
suspend fun save() {
|
|
||||||
delete()
|
|
||||||
dbQuery {
|
|
||||||
DbSchedule.insert {
|
|
||||||
it[workGroupId] = this@Schedule.workGroupId
|
|
||||||
it[day] = this@Schedule.day
|
|
||||||
it[time] = this@Schedule.time
|
|
||||||
it[roomId] = this@Schedule.roomId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete() {
|
|
||||||
dbQuery {
|
|
||||||
DbSchedule.deleteWhere {
|
|
||||||
(DbSchedule.workGroupId eq workGroupId) and
|
|
||||||
(DbSchedule.day eq day) and
|
|
||||||
(DbSchedule.time eq time) and
|
|
||||||
(DbSchedule.roomId eq roomId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun loadConstraints() {
|
|
||||||
try {
|
|
||||||
workGroup = WorkGroup.get(workGroupId)
|
|
||||||
room = Room.get(roomId)
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
delete()
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as Schedule
|
|
||||||
|
|
||||||
if (workGroupId != other.workGroupId) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return workGroupId
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private suspend fun parseQuery(block: () -> Query): List<Schedule> =
|
|
||||||
dbQuery {
|
|
||||||
val query = block()
|
|
||||||
query.map { result ->
|
|
||||||
Schedule(
|
|
||||||
result[DbSchedule.workGroupId],
|
|
||||||
result[DbSchedule.day],
|
|
||||||
result[DbSchedule.time],
|
|
||||||
result[DbSchedule.roomId]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.onEach { it.loadConstraints() }
|
|
||||||
|
|
||||||
suspend fun getByRoom(roomId: Int): List<Schedule> = parseQuery {
|
|
||||||
DbSchedule.select { DbSchedule.roomId eq roomId }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getByRoom(roomId: Int, day: Int, time: Int): List<Schedule> = parseQuery {
|
|
||||||
DbSchedule.select { (DbSchedule.roomId eq roomId) and (DbSchedule.day eq day) and (DbSchedule.time eq time) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getByWorkGroup(workGroupId: Int): List<Schedule> = parseQuery {
|
|
||||||
DbSchedule.select { DbSchedule.workGroupId eq workGroupId }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getByDay(day: Int): List<Schedule> = parseQuery {
|
|
||||||
DbSchedule.select { DbSchedule.day eq day }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun list(): List<Schedule> = parseQuery {
|
|
||||||
DbSchedule.selectAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package de.kif.backend.model
|
|
||||||
|
|
||||||
import de.kif.backend.database.DbTrack
|
|
||||||
import de.kif.backend.database.dbQuery
|
|
||||||
import kif.common.model.Color
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
|
|
||||||
class Track(
|
|
||||||
var id: Int = -1,
|
|
||||||
var name: String = "",
|
|
||||||
var color: Color
|
|
||||||
) {
|
|
||||||
suspend fun save() {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbTrack.insert {
|
|
||||||
it[name] = this@Track.name
|
|
||||||
it[color] = this@Track.color.toString()
|
|
||||||
}[DbTrack.id]!!
|
|
||||||
this@Track.id = newId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbTrack.update({ DbTrack.id eq id }) {
|
|
||||||
it[name] = this@Track.name
|
|
||||||
it[color] = this@Track.color.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete() {
|
|
||||||
val id = id
|
|
||||||
if (id >= 0) {
|
|
||||||
dbQuery {
|
|
||||||
DbTrack.deleteWhere { DbTrack.id eq id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
suspend fun get(TrackId: Int): Track = dbQuery {
|
|
||||||
val result = DbTrack.select { DbTrack.id eq TrackId }.firstOrNull() ?: throw IllegalArgumentException()
|
|
||||||
Track(
|
|
||||||
result[DbTrack.id],
|
|
||||||
result[DbTrack.name],
|
|
||||||
Color.parse(result[DbTrack.color])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun list(): List<Track> = dbQuery {
|
|
||||||
val query = DbTrack.selectAll()
|
|
||||||
query.map { result ->
|
|
||||||
Track(
|
|
||||||
result[DbTrack.id],
|
|
||||||
result[DbTrack.name],
|
|
||||||
Color.parse(result[DbTrack.color])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
package de.kif.backend.model
|
|
||||||
|
|
||||||
import de.kif.backend.database.DbUser
|
|
||||||
import de.kif.backend.database.DbUserPermission
|
|
||||||
import de.kif.backend.database.dbQuery
|
|
||||||
import io.ktor.auth.Principal
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.mindrot.jbcrypt.BCrypt
|
|
||||||
|
|
||||||
class User(
|
|
||||||
var id: Int = -1,
|
|
||||||
var username: String = "",
|
|
||||||
private var password: String = ""
|
|
||||||
) : Principal {
|
|
||||||
var permissions: Set<Permission> = emptySet()
|
|
||||||
|
|
||||||
fun checkPassword(password: String): Boolean {
|
|
||||||
return BCrypt.checkpw(password, this.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hashPassword(newPassword: String) {
|
|
||||||
password = BCrypt.hashpw(newPassword, BCrypt.gensalt())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkPermission(permission: Permission): Boolean {
|
|
||||||
return permission in permissions || Permission.ADMIN in permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun loadPermissions() = dbQuery {
|
|
||||||
permissions = DbUserPermission.slice(DbUserPermission.permission).select {
|
|
||||||
DbUserPermission.userId eq id
|
|
||||||
}.map { it[DbUserPermission.permission] }.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun save() {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbUser.insert {
|
|
||||||
it[username] = this@User.username
|
|
||||||
it[password] = this@User.password
|
|
||||||
}[DbUser.userId]!!
|
|
||||||
this@User.id = newId
|
|
||||||
|
|
||||||
for (permission in permissions) {
|
|
||||||
DbUserPermission.insert {
|
|
||||||
it[userId] = newId
|
|
||||||
it[this.permission] = permission
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbUser.update({ DbUser.userId eq id }) {
|
|
||||||
it[username] = this@User.username
|
|
||||||
it[password] = this@User.password
|
|
||||||
}
|
|
||||||
|
|
||||||
DbUserPermission.deleteWhere { DbUserPermission.userId eq id }
|
|
||||||
|
|
||||||
for (permission in permissions) {
|
|
||||||
DbUserPermission.insert {
|
|
||||||
it[userId] = id
|
|
||||||
it[this.permission] = permission
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete() {
|
|
||||||
val id = id
|
|
||||||
if (id >= 0) {
|
|
||||||
dbQuery {
|
|
||||||
DbUserPermission.deleteWhere { DbUserPermission.userId eq id }
|
|
||||||
DbUser.deleteWhere { DbUser.userId eq id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
suspend fun create(username: String, password: String, permissions: Set<Permission>) {
|
|
||||||
val user = User(username = username)
|
|
||||||
user.hashPassword(password)
|
|
||||||
user.permissions = permissions
|
|
||||||
user.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun find(username: String): User? = dbQuery {
|
|
||||||
val result = DbUser.select { DbUser.username eq username }.firstOrNull() ?: return@dbQuery null
|
|
||||||
User(result[DbUser.userId], result[DbUser.username], result[DbUser.password])
|
|
||||||
|
|
||||||
}?.apply { loadPermissions() }
|
|
||||||
|
|
||||||
suspend fun get(userId: Int): User? = dbQuery {
|
|
||||||
val result = DbUser.select { DbUser.userId eq userId }.firstOrNull() ?: return@dbQuery null
|
|
||||||
User(result[DbUser.userId], result[DbUser.username], result[DbUser.password])
|
|
||||||
}?.apply { loadPermissions() }
|
|
||||||
|
|
||||||
suspend fun list(): List<User> = dbQuery {
|
|
||||||
val query = DbUser.selectAll()
|
|
||||||
query.map { result ->
|
|
||||||
User(result[DbUser.userId], result[DbUser.username], result[DbUser.password])
|
|
||||||
}
|
|
||||||
}.onEach { it.loadPermissions() }
|
|
||||||
|
|
||||||
suspend fun exists(): Boolean = dbQuery {
|
|
||||||
DbUser.selectAll().count() == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
package de.kif.backend.model
|
|
||||||
|
|
||||||
import de.kif.backend.database.DbWorkGroup
|
|
||||||
import de.kif.backend.database.DbWorkGroupConstraint
|
|
||||||
import de.kif.backend.database.Language
|
|
||||||
import de.kif.backend.database.dbQuery
|
|
||||||
import io.ktor.features.NotFoundException
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
|
|
||||||
class WorkGroup(
|
|
||||||
var id: Int = -1,
|
|
||||||
var name: String = "",
|
|
||||||
var interested: Int = 0,
|
|
||||||
var trackId: Int? = null,
|
|
||||||
var projector: Boolean = false,
|
|
||||||
var resolution: Boolean = false,
|
|
||||||
var length: Int = 0,
|
|
||||||
var language: Language = Language.GERMAN,
|
|
||||||
var start: Long? = null,
|
|
||||||
var end: Long? = null
|
|
||||||
) {
|
|
||||||
var constraints: Set<WorkGroupConstraint> = emptySet()
|
|
||||||
var track: Track? = null
|
|
||||||
|
|
||||||
suspend fun save() {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbWorkGroup.insert {
|
|
||||||
it[name] = this@WorkGroup.name
|
|
||||||
it[interested] = this@WorkGroup.interested
|
|
||||||
it[trackId] = this@WorkGroup.trackId
|
|
||||||
it[projector] = this@WorkGroup.projector
|
|
||||||
it[resolution] = this@WorkGroup.resolution
|
|
||||||
it[length] = this@WorkGroup.length
|
|
||||||
it[language] = this@WorkGroup.language
|
|
||||||
it[start] = this@WorkGroup.start
|
|
||||||
it[end] = this@WorkGroup.end
|
|
||||||
}[DbWorkGroup.id]!!
|
|
||||||
this@WorkGroup.id = newId
|
|
||||||
}
|
|
||||||
for (constraint in constraints) {
|
|
||||||
constraint.save(this@WorkGroup.id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbWorkGroup.update({ DbWorkGroup.id eq id }) {
|
|
||||||
it[name] = this@WorkGroup.name
|
|
||||||
it[interested] = this@WorkGroup.interested
|
|
||||||
it[trackId] = this@WorkGroup.trackId
|
|
||||||
it[projector] = this@WorkGroup.projector
|
|
||||||
it[resolution] = this@WorkGroup.resolution
|
|
||||||
it[length] = this@WorkGroup.length
|
|
||||||
it[language] = this@WorkGroup.language
|
|
||||||
it[start] = this@WorkGroup.start
|
|
||||||
it[end] = this@WorkGroup.end
|
|
||||||
}
|
|
||||||
|
|
||||||
DbWorkGroupConstraint.deleteWhere { DbWorkGroupConstraint.workGroupId eq id }
|
|
||||||
}
|
|
||||||
for (constraint in constraints) {
|
|
||||||
constraint.save(this@WorkGroup.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete() {
|
|
||||||
val id = id
|
|
||||||
if (id >= 0) {
|
|
||||||
for (it in Schedule.getByWorkGroup(id)) {
|
|
||||||
it.delete()
|
|
||||||
}
|
|
||||||
dbQuery {
|
|
||||||
DbWorkGroupConstraint.deleteWhere { DbWorkGroupConstraint.workGroupId eq id }
|
|
||||||
DbWorkGroup.deleteWhere { DbWorkGroup.id eq id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun loadConstraints() {
|
|
||||||
if (id >= 0) {
|
|
||||||
constraints = WorkGroupConstraint.get(id)
|
|
||||||
track = trackId?.let { if (it < 0) null else Track.get(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
suspend fun get(workGroupId: Int): WorkGroup = dbQuery {
|
|
||||||
val result = DbWorkGroup.select { DbWorkGroup.id eq workGroupId }.firstOrNull() ?: throw IllegalArgumentException()
|
|
||||||
WorkGroup(
|
|
||||||
result[DbWorkGroup.id],
|
|
||||||
result[DbWorkGroup.name],
|
|
||||||
result[DbWorkGroup.interested],
|
|
||||||
result[DbWorkGroup.trackId],
|
|
||||||
result[DbWorkGroup.projector],
|
|
||||||
result[DbWorkGroup.resolution],
|
|
||||||
result[DbWorkGroup.length],
|
|
||||||
result[DbWorkGroup.language],
|
|
||||||
result[DbWorkGroup.start],
|
|
||||||
result[DbWorkGroup.end]
|
|
||||||
)
|
|
||||||
}.apply { loadConstraints() }
|
|
||||||
|
|
||||||
suspend fun list(): List<WorkGroup> = dbQuery {
|
|
||||||
val query = DbWorkGroup.selectAll()
|
|
||||||
query.map { result ->
|
|
||||||
WorkGroup(
|
|
||||||
result[DbWorkGroup.id],
|
|
||||||
result[DbWorkGroup.name],
|
|
||||||
result[DbWorkGroup.interested],
|
|
||||||
result[DbWorkGroup.trackId],
|
|
||||||
result[DbWorkGroup.projector],
|
|
||||||
result[DbWorkGroup.resolution],
|
|
||||||
result[DbWorkGroup.length],
|
|
||||||
result[DbWorkGroup.language],
|
|
||||||
result[DbWorkGroup.start],
|
|
||||||
result[DbWorkGroup.end]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.onEach { it.loadConstraints() }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
package de.kif.backend.model
|
|
||||||
|
|
||||||
import de.kif.backend.database.DbConstraintType
|
|
||||||
import de.kif.backend.database.DbWorkGroupConstraint
|
|
||||||
import de.kif.backend.database.dbQuery
|
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
|
||||||
import org.jetbrains.exposed.sql.insert
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.update
|
|
||||||
|
|
||||||
sealed class WorkGroupConstraint(
|
|
||||||
var id: Int = -1
|
|
||||||
) {
|
|
||||||
|
|
||||||
abstract suspend fun save(workGroupId: Int)
|
|
||||||
|
|
||||||
suspend fun delete() {
|
|
||||||
val id = id
|
|
||||||
if (id >= 0) {
|
|
||||||
dbQuery {
|
|
||||||
DbWorkGroupConstraint.deleteWhere { DbWorkGroupConstraint.id eq id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BeginOnDay(
|
|
||||||
var time: Long = 0,
|
|
||||||
var day: Int = 0
|
|
||||||
) : WorkGroupConstraint() {
|
|
||||||
override suspend fun save(workGroupId: Int) {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbWorkGroupConstraint.insert {
|
|
||||||
it[this@insert.workGroupId] = workGroupId
|
|
||||||
it[type] = DbConstraintType.BEGIN
|
|
||||||
it[time] = this@BeginOnDay.time
|
|
||||||
it[day] = this@BeginOnDay.day
|
|
||||||
}[DbWorkGroupConstraint.id]!!
|
|
||||||
this@BeginOnDay.id = newId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbWorkGroupConstraint.update({ DbWorkGroupConstraint.id eq id }) {
|
|
||||||
it[this@update.workGroupId] = workGroupId
|
|
||||||
it[type] = DbConstraintType.BEGIN
|
|
||||||
it[time] = this@BeginOnDay.time
|
|
||||||
it[day] = this@BeginOnDay.day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EndOnDay(
|
|
||||||
var time: Long = 0,
|
|
||||||
var day: Int = 0
|
|
||||||
) : WorkGroupConstraint() {
|
|
||||||
override suspend fun save(workGroupId: Int) {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbWorkGroupConstraint.insert {
|
|
||||||
it[this@insert.workGroupId] = workGroupId
|
|
||||||
it[type] = DbConstraintType.END
|
|
||||||
it[time] = this@EndOnDay.time
|
|
||||||
it[day] = this@EndOnDay.day
|
|
||||||
}[DbWorkGroupConstraint.id]!!
|
|
||||||
this@EndOnDay.id = newId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbWorkGroupConstraint.update({ DbWorkGroupConstraint.id eq id }) {
|
|
||||||
it[this@update.workGroupId] = workGroupId
|
|
||||||
it[type] = DbConstraintType.END
|
|
||||||
it[time] = this@EndOnDay.time
|
|
||||||
it[day] = this@EndOnDay.day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BlockedOnDay(
|
|
||||||
var time: Long = 0,
|
|
||||||
var duration: Int = 0,
|
|
||||||
var day: Int = 0
|
|
||||||
) : WorkGroupConstraint() {
|
|
||||||
|
|
||||||
override suspend fun save(workGroupId: Int) {
|
|
||||||
if (id < 0) {
|
|
||||||
dbQuery {
|
|
||||||
val newId = DbWorkGroupConstraint.insert {
|
|
||||||
it[this@insert.workGroupId] = workGroupId
|
|
||||||
it[type] = DbConstraintType.BLOCKED
|
|
||||||
it[time] = this@BlockedOnDay.time
|
|
||||||
it[duration] = this@BlockedOnDay.duration
|
|
||||||
it[day] = this@BlockedOnDay.day
|
|
||||||
}[DbWorkGroupConstraint.id]!!
|
|
||||||
this@BlockedOnDay.id = newId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbQuery {
|
|
||||||
DbWorkGroupConstraint.update({ DbWorkGroupConstraint.id eq id }) {
|
|
||||||
it[this@update.workGroupId] = workGroupId
|
|
||||||
it[type] = DbConstraintType.BLOCKED
|
|
||||||
it[time] = this@BlockedOnDay.time
|
|
||||||
it[duration] = this@BlockedOnDay.duration
|
|
||||||
it[day] = this@BlockedOnDay.day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
suspend fun get(workGroupId: Int): Set<WorkGroupConstraint> = dbQuery {
|
|
||||||
val result = DbWorkGroupConstraint.select { DbWorkGroupConstraint.workGroupId eq workGroupId }
|
|
||||||
result.map {
|
|
||||||
val id = it[DbWorkGroupConstraint.id]
|
|
||||||
val type = it[DbWorkGroupConstraint.type]
|
|
||||||
val time = it[DbWorkGroupConstraint.time]
|
|
||||||
val duration = it[DbWorkGroupConstraint.duration]
|
|
||||||
val day = it[DbWorkGroupConstraint.day]
|
|
||||||
|
|
||||||
when (type) {
|
|
||||||
DbConstraintType.BEGIN -> WorkGroupConstraint.BeginOnDay(time, day)
|
|
||||||
DbConstraintType.END -> WorkGroupConstraint.EndOnDay(time, day)
|
|
||||||
DbConstraintType.BLOCKED -> WorkGroupConstraint.BlockedOnDay(time, duration, day)
|
|
||||||
}.also { it.id = id }
|
|
||||||
}.toSet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package de.kif.backend.repository
|
||||||
|
|
||||||
|
import de.kif.backend.database.DbRoom
|
||||||
|
import de.kif.backend.database.dbQuery
|
||||||
|
import de.kif.backend.util.PushService
|
||||||
|
import de.kif.common.*
|
||||||
|
import de.kif.common.model.Room
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
|
object RoomRepository : Repository<Room> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private fun rowToModel(row: ResultRow): Room {
|
||||||
|
val id = row[DbRoom.id]
|
||||||
|
val name = row[DbRoom.name]
|
||||||
|
val places = row[DbRoom.places]
|
||||||
|
val projector = row[DbRoom.projector]
|
||||||
|
|
||||||
|
return Room(id, name, places, projector)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): Room? {
|
||||||
|
return dbQuery {
|
||||||
|
rowToModel(DbRoom.select { DbRoom.id eq id }.firstOrNull() ?: return@dbQuery null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: Room): Long {
|
||||||
|
return dbQuery {
|
||||||
|
val id = DbRoom.insert {
|
||||||
|
it[name] = model.name
|
||||||
|
it[places] = model.places
|
||||||
|
it[projector] = model.projector
|
||||||
|
}[DbRoom.id] ?: throw IllegalStateException("Cannot create model!")
|
||||||
|
|
||||||
|
onCreate.emit(id)
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: Room) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
dbQuery {
|
||||||
|
DbRoom.update({ DbRoom.id eq model.id }) {
|
||||||
|
it[name] = model.name
|
||||||
|
it[places] = model.places
|
||||||
|
it[projector] = model.projector
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate.emit(model.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
onDelete.emit(id)
|
||||||
|
|
||||||
|
dbQuery {
|
||||||
|
DbRoom.deleteWhere { DbRoom.id eq id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<Room> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbRoom.selectAll()
|
||||||
|
|
||||||
|
result.map(this::rowToModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerPushService() {
|
||||||
|
onCreate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.CREATE, RepositoryType.ROOM, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onUpdate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.UPDATE, RepositoryType.ROOM, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDelete {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.DELETE, RepositoryType.ROOM, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package de.kif.backend.repository
|
||||||
|
|
||||||
|
import de.kif.backend.database.DbSchedule
|
||||||
|
import de.kif.backend.database.dbQuery
|
||||||
|
import de.kif.backend.util.PushService
|
||||||
|
import de.kif.common.*
|
||||||
|
import de.kif.common.model.Schedule
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
|
object ScheduleRepository : Repository<Schedule> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private suspend fun rowToModel(row: ResultRow): Schedule {
|
||||||
|
val id = row[DbSchedule.id]
|
||||||
|
val workGroupId = row[DbSchedule.workGroupId]
|
||||||
|
val roomId = row[DbSchedule.roomId]
|
||||||
|
val day = row[DbSchedule.day]
|
||||||
|
val time = row[DbSchedule.time]
|
||||||
|
|
||||||
|
val workGroup = WorkGroupRepository.get(workGroupId)
|
||||||
|
?: throw IllegalStateException("Work group for schedule does not exist!")
|
||||||
|
val room = RoomRepository.get(roomId)
|
||||||
|
?: throw IllegalStateException("Room for schedule does not exist!")
|
||||||
|
|
||||||
|
return Schedule(id, workGroup, room, day, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): Schedule? {
|
||||||
|
return dbQuery {
|
||||||
|
val row = DbSchedule.select { DbSchedule.id eq id }.firstOrNull() ?: return@dbQuery null
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
rowToModel(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: Schedule): Long {
|
||||||
|
return dbQuery {
|
||||||
|
val id = DbSchedule.insert {
|
||||||
|
it[workGroupId] = model.workGroup.id ?: throw IllegalArgumentException("Work group does not exist!")
|
||||||
|
it[roomId] = model.room.id ?: throw IllegalArgumentException("Room does not exist!")
|
||||||
|
it[day] = model.day
|
||||||
|
it[time] = model.time
|
||||||
|
}[DbSchedule.id] ?: throw IllegalStateException("Cannot create model!")
|
||||||
|
|
||||||
|
onCreate.emit(id)
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: Schedule) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
dbQuery {
|
||||||
|
DbSchedule.update({ DbSchedule.id eq model.id }) {
|
||||||
|
it[workGroupId] = model.workGroup.id ?: throw IllegalArgumentException("Work group does not exist!")
|
||||||
|
it[roomId] = model.room.id ?: throw IllegalArgumentException("Room does not exist!")
|
||||||
|
it[day] = model.day
|
||||||
|
it[time] = model.time
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate.emit(model.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
onDelete.emit(id)
|
||||||
|
|
||||||
|
dbQuery {
|
||||||
|
DbSchedule.deleteWhere { DbSchedule.id eq id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<Schedule> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbSchedule.selectAll()
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
result.map {
|
||||||
|
rowToModel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getByDay(day: Int): List<Schedule> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbSchedule.select { DbSchedule.day eq day }
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
result.map {
|
||||||
|
rowToModel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getByWorkGroup(workGroupId: Long): List<Schedule> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbSchedule.select { DbSchedule.workGroupId eq workGroupId }
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
result.map {
|
||||||
|
rowToModel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getByRoom(roomId: Long): List<Schedule> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbSchedule.select { DbSchedule.roomId eq roomId }
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
result.map {
|
||||||
|
rowToModel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
RoomRepository.onUpdate { roomId ->
|
||||||
|
runBlocking {
|
||||||
|
getByRoom(roomId).forEach { schedule ->
|
||||||
|
if (schedule.id != null) onUpdate.emit(schedule.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomRepository.onDelete { roomId ->
|
||||||
|
runBlocking {
|
||||||
|
getByRoom(roomId).forEach { schedule ->
|
||||||
|
if (schedule.id != null) delete(schedule.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkGroupRepository.onUpdate { workGroupId ->
|
||||||
|
runBlocking {
|
||||||
|
getByWorkGroup(workGroupId).forEach { schedule ->
|
||||||
|
if (schedule.id != null) onUpdate.emit(schedule.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WorkGroupRepository.onDelete { workGroupId ->
|
||||||
|
runBlocking {
|
||||||
|
getByWorkGroup(workGroupId).forEach { schedule ->
|
||||||
|
if (schedule.id != null) delete(schedule.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerPushService() {
|
||||||
|
onCreate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.CREATE, RepositoryType.SCHEDULE, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onUpdate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.UPDATE, RepositoryType.SCHEDULE, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDelete {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.DELETE, RepositoryType.SCHEDULE, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package de.kif.backend.repository
|
||||||
|
|
||||||
|
import de.kif.backend.database.DbTrack
|
||||||
|
import de.kif.backend.database.dbQuery
|
||||||
|
import de.kif.backend.util.PushService
|
||||||
|
import de.kif.common.MessageType
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.common.model.Track
|
||||||
|
import de.kif.common.model.parseColor
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
|
object TrackRepository : Repository<Track> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private fun rowToModel(row: ResultRow): Track {
|
||||||
|
val id = row[DbTrack.id]
|
||||||
|
val name = row[DbTrack.name]
|
||||||
|
val color = row[DbTrack.color].parseColor()
|
||||||
|
|
||||||
|
return Track(id, name, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): Track? {
|
||||||
|
return dbQuery {
|
||||||
|
rowToModel(DbTrack.select { DbTrack.id eq id }.firstOrNull() ?: return@dbQuery null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: Track): Long {
|
||||||
|
return dbQuery {
|
||||||
|
val id = DbTrack.insert {
|
||||||
|
it[name] = model.name
|
||||||
|
it[color] = model.color.toString()
|
||||||
|
}[DbTrack.id] ?: throw IllegalStateException("Cannot create model!")
|
||||||
|
|
||||||
|
onCreate.emit(id)
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: Track) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
dbQuery {
|
||||||
|
DbTrack.update({ DbTrack.id eq model.id }) {
|
||||||
|
it[name] = model.name
|
||||||
|
it[color] = model.color.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate.emit(model.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
onDelete.emit(id)
|
||||||
|
|
||||||
|
dbQuery {
|
||||||
|
DbTrack.deleteWhere { DbTrack.id eq id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<Track> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbTrack.selectAll()
|
||||||
|
|
||||||
|
result.map(this::rowToModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerPushService() {
|
||||||
|
onCreate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.CREATE, RepositoryType.TRACK, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onUpdate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.UPDATE, RepositoryType.TRACK, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDelete {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.DELETE, RepositoryType.TRACK, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
src/jvmMain/kotlin/de/kif/backend/repository/UserRepository.kt
Normal file
120
src/jvmMain/kotlin/de/kif/backend/repository/UserRepository.kt
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
package de.kif.backend.repository
|
||||||
|
|
||||||
|
import de.kif.backend.database.DbUser
|
||||||
|
import de.kif.backend.database.DbUserPermission
|
||||||
|
import de.kif.backend.database.dbQuery
|
||||||
|
import de.kif.backend.util.PushService
|
||||||
|
import de.kif.common.MessageType
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.common.model.User
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
|
object UserRepository : Repository<User> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private fun rowToModel(row: ResultRow): User {
|
||||||
|
val id = row[DbUser.id]
|
||||||
|
val username = row[DbUser.username]
|
||||||
|
val password = row[DbUser.password]
|
||||||
|
|
||||||
|
val permissions = DbUserPermission.slice(DbUserPermission.permission).select {
|
||||||
|
DbUserPermission.userId eq id
|
||||||
|
}.map { it[DbUserPermission.permission] }.toSet()
|
||||||
|
|
||||||
|
return User(id, username, password, permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): User? {
|
||||||
|
return dbQuery {
|
||||||
|
rowToModel(DbUser.select { DbUser.id eq id }.firstOrNull() ?: return@dbQuery null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: User): Long {
|
||||||
|
return dbQuery {
|
||||||
|
val id = DbUser.insert {
|
||||||
|
it[username] = model.username
|
||||||
|
it[password] = model.password
|
||||||
|
}[DbUser.id] ?: throw IllegalStateException("Cannot create model!")
|
||||||
|
|
||||||
|
for (permission in model.permissions) {
|
||||||
|
DbUserPermission.insert {
|
||||||
|
it[userId] = id
|
||||||
|
it[this.permission] = permission
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCreate.emit(id)
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: User) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
dbQuery {
|
||||||
|
DbUser.update({ DbUser.id eq model.id }) {
|
||||||
|
it[username] = model.username
|
||||||
|
it[password] = model.password
|
||||||
|
}
|
||||||
|
|
||||||
|
DbUserPermission.deleteWhere { DbUserPermission.userId eq model.id }
|
||||||
|
|
||||||
|
for (permission in model.permissions) {
|
||||||
|
DbUserPermission.insert {
|
||||||
|
it[userId] = model.id
|
||||||
|
it[this.permission] = permission
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate.emit(model.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
onDelete.emit(id)
|
||||||
|
|
||||||
|
dbQuery {
|
||||||
|
DbUserPermission.deleteWhere { DbUserPermission.userId eq id }
|
||||||
|
DbUser.deleteWhere { DbUser.id eq id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<User> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbUser.selectAll()
|
||||||
|
|
||||||
|
result.map(this::rowToModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun find(username: String): User? {
|
||||||
|
return dbQuery {
|
||||||
|
rowToModel(DbUser.select { DbUser.username eq username }.firstOrNull() ?: return@dbQuery null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerPushService() {
|
||||||
|
onCreate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.CREATE, RepositoryType.USER, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onUpdate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.UPDATE, RepositoryType.USER, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDelete {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.DELETE, RepositoryType.USER, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
package de.kif.backend.repository
|
||||||
|
|
||||||
|
import de.kif.backend.database.DbWorkGroup
|
||||||
|
import de.kif.backend.database.dbQuery
|
||||||
|
import de.kif.backend.util.PushService
|
||||||
|
import de.kif.common.MessageType
|
||||||
|
import de.kif.common.Repository
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
|
import de.kif.common.model.WorkGroup
|
||||||
|
import de.westermann.kobserve.event.EventHandler
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
|
object WorkGroupRepository : Repository<WorkGroup> {
|
||||||
|
|
||||||
|
override val onCreate = EventHandler<Long>()
|
||||||
|
override val onUpdate = EventHandler<Long>()
|
||||||
|
override val onDelete = EventHandler<Long>()
|
||||||
|
|
||||||
|
private suspend fun rowToModel(row: ResultRow): WorkGroup {
|
||||||
|
val id = row[DbWorkGroup.id]
|
||||||
|
val name = row[DbWorkGroup.name]
|
||||||
|
val interested = row[DbWorkGroup.interested]
|
||||||
|
val trackId = row[DbWorkGroup.trackId]
|
||||||
|
val projector = row[DbWorkGroup.projector]
|
||||||
|
val resolution = row[DbWorkGroup.resolution]
|
||||||
|
val length = row[DbWorkGroup.length]
|
||||||
|
val language = row[DbWorkGroup.language]
|
||||||
|
|
||||||
|
val track = trackId?.let { TrackRepository.get(it) }
|
||||||
|
|
||||||
|
return WorkGroup(id, name, interested, track, projector, resolution, length, language)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(id: Long): WorkGroup? {
|
||||||
|
return dbQuery {
|
||||||
|
val row = DbWorkGroup.select { DbWorkGroup.id eq id }.firstOrNull() ?: return@dbQuery null
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
rowToModel(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(model: WorkGroup): Long {
|
||||||
|
return dbQuery {
|
||||||
|
val id = DbWorkGroup.insert {
|
||||||
|
it[name] = model.name
|
||||||
|
it[interested] = model.interested
|
||||||
|
it[trackId] = model.track?.id
|
||||||
|
it[projector] = model.projector
|
||||||
|
it[resolution] = model.resolution
|
||||||
|
it[length] = model.length
|
||||||
|
it[language] = model.language
|
||||||
|
}[DbWorkGroup.id] ?: throw IllegalStateException("Cannot create model!")
|
||||||
|
|
||||||
|
onCreate.emit(id)
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(model: WorkGroup) {
|
||||||
|
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||||
|
dbQuery {
|
||||||
|
DbWorkGroup.update({ DbWorkGroup.id eq model.id }) {
|
||||||
|
it[name] = model.name
|
||||||
|
it[interested] = model.interested
|
||||||
|
it[trackId] = model.track?.id
|
||||||
|
it[projector] = model.projector
|
||||||
|
it[resolution] = model.resolution
|
||||||
|
it[length] = model.length
|
||||||
|
it[language] = model.language
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate.emit(model.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: Long) {
|
||||||
|
onDelete.emit(id)
|
||||||
|
|
||||||
|
dbQuery {
|
||||||
|
DbWorkGroup.deleteWhere { DbWorkGroup.id eq id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun all(): List<WorkGroup> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbWorkGroup.selectAll()
|
||||||
|
|
||||||
|
result.map {
|
||||||
|
runBlocking {
|
||||||
|
rowToModel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getByTrack(trackId: Long?): List<WorkGroup> {
|
||||||
|
return dbQuery {
|
||||||
|
val result = DbWorkGroup.select { DbWorkGroup.trackId eq trackId }
|
||||||
|
|
||||||
|
result.map {
|
||||||
|
runBlocking {
|
||||||
|
rowToModel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
TrackRepository.onUpdate { roomId ->
|
||||||
|
runBlocking {
|
||||||
|
getByTrack(roomId).forEach { workGroup ->
|
||||||
|
if (workGroup.id != null) onUpdate.emit(workGroup.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TrackRepository.onDelete { roomId ->
|
||||||
|
runBlocking {
|
||||||
|
getByTrack(roomId).forEach { workGroup ->
|
||||||
|
if (workGroup.id != null) {
|
||||||
|
update(workGroup.copy(track = null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerPushService() {
|
||||||
|
onCreate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.CREATE, RepositoryType.WORK_GROUP, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onUpdate {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.UPDATE, RepositoryType.WORK_GROUP, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDelete {
|
||||||
|
runBlocking {
|
||||||
|
PushService.notify(MessageType.DELETE, RepositoryType.WORK_GROUP, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +1,17 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.LocationAccount
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.LocationLogin
|
|
||||||
import de.kif.backend.PortalSession
|
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.html.respondHtmlTemplate
|
import io.ktor.html.respondHtmlTemplate
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.request.path
|
|
||||||
import io.ktor.response.respondRedirect
|
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.sessions.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.sessions.sessions
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
fun Route.account() {
|
fun Route.account() {
|
||||||
get<LocationAccount> {
|
get("/account") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect { user ->
|
||||||
if (user == null) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -34,7 +25,7 @@ fun Route.account() {
|
||||||
+"You have the following rights: ${user.permissions}"
|
+"You have the following rights: ${user.permissions}"
|
||||||
br {}
|
br {}
|
||||||
a("/logout") {
|
a("/logout") {
|
||||||
button(classes="form-btn") {
|
button(classes = "form-btn") {
|
||||||
+"Logout"
|
+"Logout"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,38 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.LocationCalendar
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.LocationLogin
|
import de.kif.backend.isAuthenticated
|
||||||
import de.kif.backend.PortalSession
|
import de.kif.backend.repository.RoomRepository
|
||||||
import de.kif.backend.model.Permission
|
import de.kif.backend.repository.ScheduleRepository
|
||||||
import de.kif.backend.model.Room
|
import de.kif.backend.repository.WorkGroupRepository
|
||||||
import de.kif.backend.model.Schedule
|
|
||||||
import de.kif.backend.model.WorkGroup
|
|
||||||
import de.kif.backend.util.Search
|
import de.kif.backend.util.Search
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.Room
|
||||||
|
import de.kif.common.model.Schedule
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.html.insert
|
import io.ktor.html.insert
|
||||||
import io.ktor.html.respondHtmlTemplate
|
import io.ktor.html.respondHtmlTemplate
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.request.path
|
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.routing.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.sessions.get
|
|
||||||
import io.ktor.sessions.sessions
|
|
||||||
import kif.common.model.CALENDAR_GRID_WIDTH
|
|
||||||
import kif.common.model.MessageCreateCalendarEntry
|
|
||||||
import kif.common.model.MessageDeleteCalendarEntry
|
|
||||||
import kotlinx.css.CSSBuilder
|
import kotlinx.css.CSSBuilder
|
||||||
import kotlinx.css.Color
|
import kotlinx.css.Color
|
||||||
import kotlinx.css.pct
|
import kotlinx.css.pct
|
||||||
import kotlinx.css.rem
|
import kotlinx.css.rem
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
|
import kotlin.collections.set
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
const val MINUTES_OF_DAY = 24 * 60
|
const val MINUTES_OF_DAY = 24 * 60
|
||||||
|
|
||||||
private fun DIV.calendarCell(schedule: Schedule?, day: Int, time: Int) {
|
private fun DIV.calendarCell(schedule: Schedule?) {
|
||||||
if (schedule != null) {
|
if (schedule != null) {
|
||||||
span("calendar-entry") {
|
span("calendar-entry") {
|
||||||
attributes["style"] = CSSBuilder().apply {
|
attributes["style"] = CSSBuilder().apply {
|
||||||
|
@ -50,42 +48,38 @@ private fun DIV.calendarCell(schedule: Schedule?, day: Int, time: Int) {
|
||||||
val c = schedule.workGroup.track?.color
|
val c = schedule.workGroup.track?.color
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
backgroundColor = Color(c.toString())
|
backgroundColor = Color(c.toString())
|
||||||
color = Color(c.textColor.toString())
|
color = Color(c.calcTextColor().toString())
|
||||||
}
|
}
|
||||||
}.toString()
|
}.toString()
|
||||||
attributes["data-language"] = schedule.workGroup.language.code
|
attributes["data-language"] = schedule.workGroup.language.code
|
||||||
attributes["data-day"] = schedule.day.toString()
|
attributes["data-id"] = schedule.id.toString()
|
||||||
attributes["data-room"] = schedule.room.id.toString()
|
|
||||||
attributes["data-time"] = schedule.time.toString()
|
|
||||||
attributes["data-cell-time"] = time.toString()
|
|
||||||
attributes["data-workgroup"] = schedule.workGroup.id.toString()
|
|
||||||
|
|
||||||
+schedule.workGroup.name
|
+schedule.workGroup.name
|
||||||
|
|
||||||
div("calendar-tools") {
|
div("calendar-tools") {
|
||||||
a(
|
a(
|
||||||
classes = "calendar-tools-m10",
|
classes = "calendar-tools-m10",
|
||||||
href = "/calendar/$day/${schedule.room.id}/${schedule.time}/-1?next=/calendar/$day/${schedule.room.id}/${schedule.time - 10}/${schedule.workGroupId}"
|
href = "/calendar/${schedule.day}/${schedule.id}/delete?redirect=/calendar/${schedule.day}/${schedule.room.id}/${schedule.time - 10}/${schedule.workGroup.id}"
|
||||||
) { +"-10" }
|
) { +"-10" }
|
||||||
a(
|
a(
|
||||||
classes = "calendar-tools-m5",
|
classes = "calendar-tools-m5",
|
||||||
href = "/calendar/$day/${schedule.room.id}/${schedule.time}/-1?next=/calendar/$day/${schedule.room.id}/${schedule.time - 5}/${schedule.workGroupId}"
|
href = "/calendar/${schedule.day}/${schedule.id}/delete?redirect=/calendar/${schedule.day}/${schedule.room.id}/${schedule.time - 5}/${schedule.workGroup.id}"
|
||||||
) { +"-05" }
|
) { +"-05" }
|
||||||
a(
|
a(
|
||||||
classes = "calendar-tools-reset",
|
classes = "calendar-tools-reset",
|
||||||
href = "/calendar/$day/${schedule.room.id}/${schedule.time}/-1?next=/calendar/$day/${schedule.room.id}/$time/${schedule.workGroupId}"
|
href = "/calendar/${schedule.day}/${schedule.id}/delete?redirect=/calendar/${schedule.day}/${schedule.room.id}/${schedule.time / CALENDAR_GRID_WIDTH * CALENDAR_GRID_WIDTH}/${schedule.workGroup.id}"
|
||||||
) { +"reset" }
|
) { +"reset" }
|
||||||
a(
|
a(
|
||||||
classes = "calendar-tools-p5",
|
classes = "calendar-tools-p5",
|
||||||
href = "/calendar/$day/${schedule.room.id}/${schedule.time}/-1?next=/calendar/$day/${schedule.room.id}/${schedule.time + 5}/${schedule.workGroupId}"
|
href = "/calendar/${schedule.day}/${schedule.id}/delete?redirect=/calendar/${schedule.day}/${schedule.room.id}/${schedule.time + 5}/${schedule.workGroup.id}"
|
||||||
) { +"+05" }
|
) { +"+05" }
|
||||||
a(
|
a(
|
||||||
classes = "calendar-tools-p10",
|
classes = "calendar-tools-p10",
|
||||||
href = "/calendar/$day/${schedule.room.id}/${schedule.time}/-1?next=/calendar/$day/${schedule.room.id}/${schedule.time + 10}/${schedule.workGroupId}"
|
href = "/calendar/${schedule.day}/${schedule.id}/delete?redirect=/calendar/${schedule.day}/${schedule.room.id}/${schedule.time + 10}/${schedule.workGroup.id}"
|
||||||
) { +"+10" }
|
) { +"+10" }
|
||||||
a(
|
a(
|
||||||
classes = "calendar-tools-del",
|
classes = "calendar-tools-del",
|
||||||
href = "/calendar/$day/${schedule.room.id}/${schedule.time}/-1"
|
href = "/calendar/${schedule.day}/${schedule.id}/delete"
|
||||||
) { +"del" }
|
) { +"del" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +142,7 @@ private fun DIV.renderTimeToRoom(
|
||||||
|
|
||||||
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
|
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
|
||||||
|
|
||||||
calendarCell(schedule, day, time)
|
calendarCell(schedule)
|
||||||
|
|
||||||
val href = if (allowEdit) "/calendar/$day/${room.id}/$start" else null
|
val href = if (allowEdit) "/calendar/$day/${room.id}/$start" else null
|
||||||
a(href, classes = "calendar-link")
|
a(href, classes = "calendar-link")
|
||||||
|
@ -216,7 +210,7 @@ private fun DIV.renderRoomToTime(
|
||||||
|
|
||||||
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
|
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
|
||||||
|
|
||||||
calendarCell(schedule, day, time)
|
calendarCell(schedule)
|
||||||
|
|
||||||
val href = if (allowEdit) "/calendar/$day/${room.id}/$start" else null
|
val href = if (allowEdit) "/calendar/$day/${room.id}/$start" else null
|
||||||
a(href, classes = "calendar-link")
|
a(href, classes = "calendar-link")
|
||||||
|
@ -233,37 +227,40 @@ fun Route.calendar() {
|
||||||
call.respondRedirect("/calendar/0", true)
|
call.respondRedirect("/calendar/0", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationCalendar.LocationCalendarRoomToTime> { param ->
|
get("/calendar/{day}/rtt") {
|
||||||
call.response.cookies.append(
|
call.response.cookies.append(
|
||||||
"orientation",
|
"orientation",
|
||||||
CalendarOrientation.ROOM_TO_TIME.name,
|
CalendarOrientation.ROOM_TO_TIME.name,
|
||||||
maxAge = Int.MAX_VALUE,
|
maxAge = Int.MAX_VALUE,
|
||||||
path = "/"
|
path = "/"
|
||||||
)
|
)
|
||||||
call.respondRedirect("/calendar/${param.day}")
|
val day = call.parameters["day"]?.toIntOrNull() ?: 0
|
||||||
|
call.respondRedirect("/calendar/$day")
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationCalendar.LocationCalendarTimeToRoom> { param ->
|
get("/calendar/{day}/ttr") {
|
||||||
call.response.cookies.append(
|
call.response.cookies.append(
|
||||||
"orientation",
|
"orientation",
|
||||||
CalendarOrientation.TIME_TO_ROOM.name,
|
CalendarOrientation.TIME_TO_ROOM.name,
|
||||||
maxAge = Int.MAX_VALUE,
|
maxAge = Int.MAX_VALUE,
|
||||||
path = "/"
|
path = "/"
|
||||||
)
|
)
|
||||||
call.respondRedirect("/calendar/${param.day}")
|
val day = call.parameters["day"]?.toIntOrNull() ?: 0
|
||||||
|
call.respondRedirect("/calendar/$day")
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationCalendar> { param ->
|
get("/calendar/{day}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
val user = isAuthenticated(Permission.SCHEDULE)
|
||||||
val allowEdit = user?.checkPermission(Permission.SCHEDULE) ?: false
|
|
||||||
val rooms = Room.list()
|
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
|
||||||
|
|
||||||
|
val rooms = RoomRepository.all()
|
||||||
|
|
||||||
val orientation = call.request.cookies["orientation"]?.let { name ->
|
val orientation = call.request.cookies["orientation"]?.let { name ->
|
||||||
CalendarOrientation.values().find { it.name == name }
|
CalendarOrientation.values().find { it.name == name }
|
||||||
} ?: CalendarOrientation.ROOM_TO_TIME
|
} ?: CalendarOrientation.ROOM_TO_TIME
|
||||||
|
|
||||||
val day = param.day
|
val h = ScheduleRepository.getByDay(day)
|
||||||
val h = Schedule.getByDay(day)
|
|
||||||
val schedules = h.groupBy { it.room }.mapValues { (_, it) ->
|
val schedules = h.groupBy { it.room }.mapValues { (_, it) ->
|
||||||
it.associateBy {
|
it.associateBy {
|
||||||
it.time
|
it.time
|
||||||
|
@ -327,7 +324,7 @@ fun Route.calendar() {
|
||||||
max,
|
max,
|
||||||
rooms,
|
rooms,
|
||||||
schedules,
|
schedules,
|
||||||
allowEdit
|
user != null
|
||||||
)
|
)
|
||||||
CalendarOrientation.TIME_TO_ROOM -> renderTimeToRoom(
|
CalendarOrientation.TIME_TO_ROOM -> renderTimeToRoom(
|
||||||
day,
|
day,
|
||||||
|
@ -335,7 +332,7 @@ fun Route.calendar() {
|
||||||
max,
|
max,
|
||||||
rooms,
|
rooms,
|
||||||
schedules,
|
schedules,
|
||||||
allowEdit
|
user != null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -345,15 +342,15 @@ fun Route.calendar() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationCalendar.LocationCalendarEdit> { param ->
|
get("/calendar/{day}/{room}/{time}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.SCHEDULE) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.SCHEDULE)) {
|
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val time = call.parameters["time"]?.toIntOrNull() ?: return@get
|
||||||
} else {
|
val roomId = call.parameters["room"]?.toLongOrNull() ?: return@get
|
||||||
val list = WorkGroup.list()
|
val search = call.parameters["search"] ?: ""
|
||||||
val room = Room.get(param.room)
|
|
||||||
val day = param.day
|
val list = WorkGroupRepository.all()
|
||||||
val time = param.time
|
val room = RoomRepository.get(roomId) ?: return@get
|
||||||
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
|
@ -363,12 +360,12 @@ fun Route.calendar() {
|
||||||
content {
|
content {
|
||||||
h1 { +"Select work groups" }
|
h1 { +"Select work groups" }
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = param.search
|
searchValue = search
|
||||||
|
|
||||||
action {
|
action {
|
||||||
a("/calendar/$day/${room.id}/$time/-1") {
|
a("/calendar/$day") {
|
||||||
button(classes = "form-btn btn-primary") {
|
button(classes = "form-btn btn-primary") {
|
||||||
+"Delete"
|
+"Cancel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,7 +392,7 @@ fun Route.calendar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(param.search, u.name)) {
|
if (Search.match(search, u.name)) {
|
||||||
val href = "/calendar/$day/${room.id}/$time/${u.id}"
|
val href = "/calendar/$day/${room.id}/$time/${u.id}"
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.name)
|
attributes["data-search"] = Search.pack(u.name)
|
||||||
|
@ -427,46 +424,33 @@ fun Route.calendar() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
get("/calendar/{day}/{room}/{time}/{workgroup}") {
|
||||||
|
authenticateOrRedirect(Permission.SCHEDULE) { user ->
|
||||||
|
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
|
||||||
|
val time = call.parameters["time"]?.toIntOrNull() ?: return@get
|
||||||
|
val roomId = call.parameters["room"]?.toLongOrNull() ?: return@get
|
||||||
|
val workGroupId = call.parameters["workgroup"]?.toLongOrNull() ?: return@get
|
||||||
|
|
||||||
get<LocationCalendar.LocationCalendarSet> { param ->
|
val room = RoomRepository.get(roomId) ?: return@get
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
val workGroup = WorkGroupRepository.get(workGroupId) ?: return@get
|
||||||
if (user == null || !user.checkPermission(Permission.SCHEDULE)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val schedule = Schedule(null, workGroup, room, day, time)
|
||||||
} else {
|
ScheduleRepository.create(schedule)
|
||||||
for (it in Schedule.getByRoom(param.room, param.day, param.time)) {
|
|
||||||
PushService.notify(
|
val redirect = call.parameters["redirect"]
|
||||||
MessageDeleteCalendarEntry(
|
call.respondRedirect(redirect ?: "/calendar/$day")
|
||||||
it.day,
|
|
||||||
it.time,
|
|
||||||
it.room.id,
|
|
||||||
it.workGroup.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
it.delete()
|
|
||||||
}
|
}
|
||||||
if (param.workGroup >= 0) {
|
|
||||||
val schedule = Schedule(param.workGroup, param.day, param.time, param.room)
|
|
||||||
schedule.save()
|
|
||||||
schedule.loadConstraints()
|
|
||||||
|
|
||||||
val cellTime = (schedule.time / 15) * 15
|
|
||||||
|
|
||||||
PushService.notify(
|
|
||||||
MessageCreateCalendarEntry(
|
|
||||||
schedule.day,
|
|
||||||
schedule.time,
|
|
||||||
cellTime,
|
|
||||||
schedule.room.id,
|
|
||||||
schedule.workGroup.id,
|
|
||||||
schedule.workGroup.name,
|
|
||||||
schedule.workGroup.length,
|
|
||||||
schedule.workGroup.language.code,
|
|
||||||
schedule.workGroup.track?.color
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
call.respondRedirect(param.next ?: "/calendar/${param.day}")
|
get("/calendar/{day}/{schedule}/delete") {
|
||||||
|
authenticateOrRedirect(Permission.SCHEDULE) { user ->
|
||||||
|
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
|
||||||
|
val scheduleId = call.parameters["schedule"]?.toLongOrNull() ?: return@get
|
||||||
|
|
||||||
|
ScheduleRepository.delete(scheduleId)
|
||||||
|
|
||||||
|
val redirect = call.parameters["redirect"]
|
||||||
|
call.respondRedirect(redirect ?: "/calendar/$day")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.LocationDashboard
|
|
||||||
import de.kif.backend.PortalSession
|
import de.kif.backend.PortalSession
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.html.respondHtmlTemplate
|
import io.ktor.html.respondHtmlTemplate
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.routing.get
|
||||||
import io.ktor.sessions.get
|
import io.ktor.sessions.get
|
||||||
import io.ktor.sessions.sessions
|
import io.ktor.sessions.sessions
|
||||||
import kotlinx.html.h1
|
import kotlinx.html.h1
|
||||||
|
|
||||||
fun Route.dashboard() {
|
fun Route.dashboard() {
|
||||||
get<LocationDashboard> {
|
get("") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.LocationLogin
|
|
||||||
import de.kif.backend.LocationLogout
|
|
||||||
import de.kif.backend.PortalSession
|
import de.kif.backend.PortalSession
|
||||||
import de.kif.backend.model.User
|
import de.kif.backend.UserPrinciple
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.auth.authenticate
|
import io.ktor.auth.authenticate
|
||||||
import io.ktor.auth.principal
|
import io.ktor.auth.principal
|
||||||
import io.ktor.html.respondHtmlTemplate
|
import io.ktor.html.respondHtmlTemplate
|
||||||
import io.ktor.locations.location
|
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.routing.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.routing.post
|
import io.ktor.routing.post
|
||||||
|
import io.ktor.routing.route
|
||||||
import io.ktor.sessions.clear
|
import io.ktor.sessions.clear
|
||||||
import io.ktor.sessions.get
|
import io.ktor.sessions.get
|
||||||
import io.ktor.sessions.sessions
|
import io.ktor.sessions.sessions
|
||||||
|
@ -21,12 +19,13 @@ import io.ktor.sessions.set
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
fun Route.login() {
|
fun Route.login() {
|
||||||
location<LocationLogin> {
|
route("login") {
|
||||||
authenticate {
|
authenticate {
|
||||||
post {
|
post {
|
||||||
val principal = call.principal<User>() ?: return@post
|
val principal = call.principal<UserPrinciple>() ?: return@post
|
||||||
call.sessions.set(PortalSession(principal.id, principal.username))
|
if (principal.user.id == null) return@post
|
||||||
call.respondRedirect(call.parameters[LocationLogin::next.name] ?: "/")
|
call.sessions.set(PortalSession(principal.user.id))
|
||||||
|
call.respondRedirect(call.parameters["redirect"] ?: "/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,43 +40,43 @@ fun Route.login() {
|
||||||
form("/login", method = FormMethod.post) {
|
form("/login", method = FormMethod.post) {
|
||||||
div("form-group") {
|
div("form-group") {
|
||||||
label {
|
label {
|
||||||
htmlFor = LocationLogin::username.name
|
htmlFor = "username"
|
||||||
+"Username"
|
+"Username"
|
||||||
}
|
}
|
||||||
input(
|
input(
|
||||||
name = LocationLogin::username.name,
|
name = "username",
|
||||||
classes = "form-control"
|
classes = "form-control"
|
||||||
) {
|
) {
|
||||||
id = LocationLogin::username.name
|
id = "username"
|
||||||
placeholder = "Username"
|
placeholder = "Username"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("form-group") {
|
div("form-group") {
|
||||||
label {
|
label {
|
||||||
htmlFor = LocationLogin::password.name
|
htmlFor = "password"
|
||||||
+"Password"
|
+"Password"
|
||||||
}
|
}
|
||||||
input(
|
input(
|
||||||
name = LocationLogin::password.name,
|
name = "password",
|
||||||
classes = "form-control",
|
classes = "form-control",
|
||||||
type = InputType.password
|
type = InputType.password
|
||||||
) {
|
) {
|
||||||
id = LocationLogin::password.name
|
id = "password"
|
||||||
placeholder = "Password"
|
placeholder = "Password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input(
|
input(
|
||||||
name = LocationLogin::next.name,
|
name = "redirect",
|
||||||
type = InputType.hidden
|
type = InputType.hidden
|
||||||
) {
|
) {
|
||||||
value = call.parameters[LocationLogin::next.name] ?: "/"
|
value = call.parameters["redirect"] ?: "/"
|
||||||
}
|
}
|
||||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
||||||
+"Login"
|
+"Login"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("error" in call.parameters) {
|
if ("onFailure" in call.parameters) {
|
||||||
br { }
|
br { }
|
||||||
div("alert alert-danger") {
|
div("alert alert-danger") {
|
||||||
+"Username or password incorrect!"
|
+"Username or password incorrect!"
|
||||||
|
@ -88,15 +87,13 @@ fun Route.login() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
call.respondRedirect(call.parameters[LocationLogin::next.name] ?: "/")
|
call.respondRedirect(call.parameters["redirect"] ?: "/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
location<LocationLogout> {
|
get("logout") {
|
||||||
get {
|
|
||||||
call.sessions.clear<PortalSession>()
|
call.sessions.clear<PortalSession>()
|
||||||
call.respondRedirect("/")
|
call.respondRedirect("/")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,258 +0,0 @@
|
||||||
package de.kif.backend.route
|
|
||||||
|
|
||||||
import de.kif.backend.LocationLogin
|
|
||||||
import de.kif.backend.LocationPerson
|
|
||||||
import de.kif.backend.PortalSession
|
|
||||||
import de.kif.backend.model.Permission
|
|
||||||
import de.kif.backend.model.Person
|
|
||||||
import de.kif.backend.util.Search
|
|
||||||
import de.kif.backend.view.MainTemplate
|
|
||||||
import de.kif.backend.view.MenuTemplate
|
|
||||||
import de.kif.backend.view.TableTemplate
|
|
||||||
import io.ktor.application.call
|
|
||||||
import io.ktor.html.insert
|
|
||||||
import io.ktor.html.respondHtmlTemplate
|
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.locations.post
|
|
||||||
import io.ktor.request.path
|
|
||||||
import io.ktor.request.receiveParameters
|
|
||||||
import io.ktor.response.respondRedirect
|
|
||||||
import io.ktor.routing.Route
|
|
||||||
import io.ktor.sessions.get
|
|
||||||
import io.ktor.sessions.sessions
|
|
||||||
import io.ktor.util.toMap
|
|
||||||
import kotlinx.html.*
|
|
||||||
|
|
||||||
fun Route.person() {
|
|
||||||
get<LocationPerson> { param ->
|
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
|
||||||
if (user == null || !user.checkPermission(Permission.PERSON)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val list = Person.list()
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.PERSON
|
|
||||||
}
|
|
||||||
content {
|
|
||||||
h1 { +"Persons" }
|
|
||||||
insert(TableTemplate()) {
|
|
||||||
searchValue = param.search
|
|
||||||
|
|
||||||
action {
|
|
||||||
a("/person/new") {
|
|
||||||
button(classes="form-btn btn-primary") {
|
|
||||||
+"Add person"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
th {
|
|
||||||
+"First name"
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
+"Last name"
|
|
||||||
}
|
|
||||||
th(classes = "action") {
|
|
||||||
+"Action"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (u in list) {
|
|
||||||
if (Search.match(param.search, u.firstName, u.lastName)) {
|
|
||||||
entry {
|
|
||||||
attributes["data-search"] = Search.pack(u.firstName, u.lastName)
|
|
||||||
td {
|
|
||||||
+u.firstName
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
+u.lastName
|
|
||||||
}
|
|
||||||
td(classes = "action") {
|
|
||||||
a("/person/${u.id}") {
|
|
||||||
i("material-icons") { +"edit" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get<LocationPerson.Edit> { personId ->
|
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
|
||||||
if (user == null || !user.checkPermission(Permission.PERSON)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val editPerson = Person.get(personId.id) ?: return@get
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.PERSON
|
|
||||||
}
|
|
||||||
content {
|
|
||||||
h1 { +"Edit person" }
|
|
||||||
form(method = FormMethod.post) {
|
|
||||||
div("form-group") {
|
|
||||||
label {
|
|
||||||
htmlFor = "first-name"
|
|
||||||
+"First name"
|
|
||||||
}
|
|
||||||
input(
|
|
||||||
name = "first-name",
|
|
||||||
classes = "form-control"
|
|
||||||
) {
|
|
||||||
id = "first-name"
|
|
||||||
placeholder = "First name"
|
|
||||||
value = editPerson.firstName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div("form-group") {
|
|
||||||
label {
|
|
||||||
htmlFor = "last-name"
|
|
||||||
+"Last name"
|
|
||||||
}
|
|
||||||
input(
|
|
||||||
name = "last-name",
|
|
||||||
classes = "form-control"
|
|
||||||
) {
|
|
||||||
id = "last-name"
|
|
||||||
placeholder = "Last name"
|
|
||||||
value = editPerson.lastName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div("form-group") {
|
|
||||||
a("/person") {
|
|
||||||
button(classes = "form-btn") {
|
|
||||||
+"Cancel"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
|
||||||
+"Save"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a("/person/${editPerson.id}/delete") {
|
|
||||||
button(classes = "form-btn btn-danger") {
|
|
||||||
+"Delete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post<LocationPerson.Edit> { personId ->
|
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
|
||||||
if (user == null || !user.checkPermission(Permission.PERSON)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
|
||||||
list.firstOrNull()
|
|
||||||
}
|
|
||||||
val editPerson = Person.get(personId.id) ?: return@post
|
|
||||||
|
|
||||||
params["first-name"]?.let { editPerson.firstName = it }
|
|
||||||
params["last-name"]?.let { editPerson.lastName = it }
|
|
||||||
|
|
||||||
editPerson.save()
|
|
||||||
|
|
||||||
call.respondRedirect("/person")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get<LocationPerson.New> {
|
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
|
||||||
if (user == null || !user.checkPermission(Permission.PERSON)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
|
||||||
menuTemplate {
|
|
||||||
this.user = user
|
|
||||||
active = MenuTemplate.Tab.PERSON
|
|
||||||
}
|
|
||||||
content {
|
|
||||||
h1 { +"Create person" }
|
|
||||||
form(method = FormMethod.post) {
|
|
||||||
div("form-group") {
|
|
||||||
label {
|
|
||||||
htmlFor = "first-name"
|
|
||||||
+"First name"
|
|
||||||
}
|
|
||||||
input(
|
|
||||||
name = "first-name",
|
|
||||||
classes = "form-control"
|
|
||||||
) {
|
|
||||||
id = "first-name"
|
|
||||||
placeholder = "First name"
|
|
||||||
value = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div("form-group") {
|
|
||||||
label {
|
|
||||||
htmlFor = "last-name"
|
|
||||||
+"Last name"
|
|
||||||
}
|
|
||||||
input(
|
|
||||||
name = "last-name",
|
|
||||||
classes = "form-control"
|
|
||||||
) {
|
|
||||||
id = "last-name"
|
|
||||||
placeholder = "Last name"
|
|
||||||
value = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div("form-group") {
|
|
||||||
a("/person") {
|
|
||||||
button(classes = "form-btn") {
|
|
||||||
+"Cancel"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
|
||||||
+"Create"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post<LocationPerson.New> {
|
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
|
||||||
if (user == null || !user.checkPermission(Permission.PERSON)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
|
||||||
list.firstOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
val firstName = params["first-name"] ?: return@post
|
|
||||||
val lastName = params["last-name"] ?: return@post
|
|
||||||
|
|
||||||
Person(firstName = firstName, lastName = lastName).save()
|
|
||||||
|
|
||||||
call.respondRedirect("/person")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get<LocationPerson.Delete> { personId ->
|
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
|
||||||
if (user == null || !user.checkPermission(Permission.PERSON)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val deletePerson = Person.get(personId.id) ?: return@get
|
|
||||||
|
|
||||||
deletePerson.delete()
|
|
||||||
|
|
||||||
call.respondRedirect("/person")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +1,35 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.LocationLogin
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.LocationRoom
|
import de.kif.backend.repository.RoomRepository
|
||||||
import de.kif.backend.PortalSession
|
|
||||||
import de.kif.backend.model.Permission
|
|
||||||
import de.kif.backend.model.Room
|
|
||||||
import de.kif.backend.util.Search
|
import de.kif.backend.util.Search
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.Room
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.html.insert
|
import io.ktor.html.insert
|
||||||
import io.ktor.html.respondHtmlTemplate
|
import io.ktor.html.respondHtmlTemplate
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.locations.post
|
|
||||||
import io.ktor.request.path
|
|
||||||
import io.ktor.request.receiveParameters
|
import io.ktor.request.receiveParameters
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.sessions.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.sessions.sessions
|
import io.ktor.routing.post
|
||||||
import io.ktor.util.toMap
|
import io.ktor.util.toMap
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
|
import kotlin.collections.firstOrNull
|
||||||
|
import kotlin.collections.mapValues
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
fun Route.room() {
|
fun Route.room() {
|
||||||
get<LocationRoom> { param ->
|
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
get("/rooms") {
|
||||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val search = call.parameters["search"] ?: ""
|
||||||
} else {
|
val list = RoomRepository.all()
|
||||||
val list = Room.list()
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -38,11 +38,11 @@ fun Route.room() {
|
||||||
content {
|
content {
|
||||||
h1 { +"Rooms" }
|
h1 { +"Rooms" }
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = param.search
|
searchValue = search
|
||||||
|
|
||||||
action {
|
action {
|
||||||
a("/room/new") {
|
a("/room/new") {
|
||||||
button(classes="form-btn btn-primary") {
|
button(classes = "form-btn btn-primary") {
|
||||||
+"Add room"
|
+"Add room"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ fun Route.room() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(param.search, u.name)) {
|
if (Search.match(search, u.name)) {
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.name)
|
attributes["data-search"] = Search.pack(u.name)
|
||||||
td {
|
td {
|
||||||
|
@ -90,12 +90,10 @@ fun Route.room() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationRoom.Edit> { roomId ->
|
get("/room/{id}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val editRoom = RoomRepository.get(roomId) ?: return@get
|
||||||
} else {
|
|
||||||
val editRoom = Room.get(roomId.id)
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -175,31 +173,26 @@ fun Route.room() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<LocationRoom.Edit> { roomId ->
|
post("/room/{id}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||||
list.firstOrNull()
|
list.firstOrNull()
|
||||||
}
|
}
|
||||||
val editRoom = Room.get(roomId.id)
|
var room = RoomRepository.get(roomId) ?: return@post
|
||||||
|
|
||||||
params["name"]?.let { editRoom.name = it }
|
params["name"]?.let { room = room.copy(name = it) }
|
||||||
params["places"]?.let { editRoom.places = it.toIntOrNull() ?: 0 }
|
params["places"]?.let { room = room.copy(places = it.toIntOrNull() ?: 0) }
|
||||||
params["projector"]?.let { editRoom.projector = it == "on" }
|
params["projector"]?.let { room = room.copy(projector = it == "on") }
|
||||||
|
|
||||||
editRoom.save()
|
RoomRepository.update(room)
|
||||||
|
|
||||||
call.respondRedirect("/room")
|
call.respondRedirect("/rooms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationRoom.New> {
|
get("/room/new") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -274,11 +267,8 @@ fun Route.room() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<LocationRoom.New> {
|
post("/room/new") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||||
list.firstOrNull()
|
list.firstOrNull()
|
||||||
}
|
}
|
||||||
|
@ -287,22 +277,21 @@ fun Route.room() {
|
||||||
val places = (params["places"] ?: return@post).toIntOrNull() ?: 0
|
val places = (params["places"] ?: return@post).toIntOrNull() ?: 0
|
||||||
val projector = params["projector"] == "on"
|
val projector = params["projector"] == "on"
|
||||||
|
|
||||||
Room(name = name, places = places, projector = projector).save()
|
val room = Room(null, name, places, projector)
|
||||||
|
|
||||||
call.respondRedirect("/room")
|
RoomRepository.create(room)
|
||||||
|
|
||||||
|
call.respondRedirect("/rooms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationRoom.Delete> { roomId ->
|
get("/room/{id}/delete") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val deleteRoom = Room.get(roomId.id)
|
|
||||||
|
|
||||||
deleteRoom.delete()
|
RoomRepository.delete(roomId)
|
||||||
|
|
||||||
call.respondRedirect("/room")
|
call.respondRedirect("/rooms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
package de.kif.backend.route
|
|
||||||
|
|
||||||
import de.kif.backend.LocationLogin
|
|
||||||
import de.kif.backend.database.DbUser
|
|
||||||
import de.kif.backend.model.Permission
|
|
||||||
import de.kif.backend.model.User
|
|
||||||
import de.kif.backend.view.MainTemplate
|
|
||||||
import io.ktor.application.call
|
|
||||||
import io.ktor.html.respondHtmlTemplate
|
|
||||||
import io.ktor.request.receiveParameters
|
|
||||||
import io.ktor.response.respondRedirect
|
|
||||||
import io.ktor.routing.Route
|
|
||||||
import io.ktor.routing.get
|
|
||||||
import io.ktor.routing.post
|
|
||||||
import io.ktor.routing.route
|
|
||||||
import kotlinx.html.*
|
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
|
|
||||||
fun Route.setup() {
|
|
||||||
route("/") {
|
|
||||||
get {
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
|
||||||
transaction {
|
|
||||||
val firstStart = DbUser.selectAll().count() == 0
|
|
||||||
|
|
||||||
menuTemplate {
|
|
||||||
setup = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstStart) {
|
|
||||||
content {
|
|
||||||
div {
|
|
||||||
h1 { +"Create account" }
|
|
||||||
form("/", method = FormMethod.post) {
|
|
||||||
div("form-group") {
|
|
||||||
label {
|
|
||||||
htmlFor = LocationLogin::username.name
|
|
||||||
+"Username"
|
|
||||||
}
|
|
||||||
input(
|
|
||||||
name = LocationLogin::username.name,
|
|
||||||
classes = "form-control"
|
|
||||||
) {
|
|
||||||
id = LocationLogin::username.name
|
|
||||||
placeholder = "Username"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div("form-group") {
|
|
||||||
label {
|
|
||||||
htmlFor = LocationLogin::password.name
|
|
||||||
+"Password"
|
|
||||||
}
|
|
||||||
input(
|
|
||||||
name = LocationLogin::password.name,
|
|
||||||
classes = "form-control",
|
|
||||||
type = InputType.password
|
|
||||||
) {
|
|
||||||
id = LocationLogin::password.name
|
|
||||||
placeholder = "Password"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button(type = ButtonType.submit, classes = "btn btn-primary") {
|
|
||||||
+"Create"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
content {
|
|
||||||
div {
|
|
||||||
h1 { +"Setup complete" }
|
|
||||||
p {
|
|
||||||
+"Please restart the server!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
val parameters = call.receiveParameters()
|
|
||||||
val username = parameters[LocationLogin::username.name]
|
|
||||||
val password = parameters[LocationLogin::password.name]
|
|
||||||
|
|
||||||
if (username == null || password == null) {
|
|
||||||
call.respondRedirect("/")
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
User.create(username, password, Permission.values().toSet())
|
|
||||||
call.respondRedirect("/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get("*") {
|
|
||||||
call.respondRedirect("/")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +1,27 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.LocationLogin
|
|
||||||
import de.kif.backend.LocationTrack
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.PortalSession
|
import de.kif.backend.repository.TrackRepository
|
||||||
import de.kif.backend.model.Permission
|
|
||||||
import de.kif.backend.model.Track
|
|
||||||
import de.kif.backend.util.Search
|
import de.kif.backend.util.Search
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
import de.kif.common.model.Color
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.Track
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.html.insert
|
import io.ktor.html.insert
|
||||||
import io.ktor.html.respondHtmlTemplate
|
import io.ktor.html.respondHtmlTemplate
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.locations.post
|
|
||||||
import io.ktor.request.path
|
|
||||||
import io.ktor.request.receiveParameters
|
import io.ktor.request.receiveParameters
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.sessions.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.sessions.sessions
|
import io.ktor.routing.post
|
||||||
import io.ktor.util.toMap
|
import io.ktor.util.toMap
|
||||||
import kif.common.model.Color
|
|
||||||
import kotlinx.css.CSSBuilder
|
import kotlinx.css.CSSBuilder
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
import kotlin.collections.set
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
fun DIV.colorPicker(color: Color?) {
|
fun DIV.colorPicker(color: Color?) {
|
||||||
|
@ -85,12 +83,10 @@ fun DIV.colorPicker(color: Color?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Route.track() {
|
fun Route.track() {
|
||||||
get<LocationTrack> { param ->
|
get("/tracks") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val search = call.parameters["search"] ?: ""
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val list = TrackRepository.all()
|
||||||
} else {
|
|
||||||
val list = Track.list()
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -99,7 +95,7 @@ fun Route.track() {
|
||||||
content {
|
content {
|
||||||
h1 { +"Tracks" }
|
h1 { +"Tracks" }
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = param.search
|
searchValue = search
|
||||||
|
|
||||||
action {
|
action {
|
||||||
a("/track/new") {
|
a("/track/new") {
|
||||||
|
@ -122,7 +118,7 @@ fun Route.track() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(param.search, u.name)) {
|
if (Search.match(search, u.name)) {
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.name)
|
attributes["data-search"] = Search.pack(u.name)
|
||||||
td {
|
td {
|
||||||
|
@ -145,12 +141,10 @@ fun Route.track() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationTrack.Edit> { trackId ->
|
get("/track/{id}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val editTrack = TrackRepository.get(trackId) ?: return@get
|
||||||
} else {
|
|
||||||
val editTrack = Track.get(trackId.id)
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -198,33 +192,28 @@ fun Route.track() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<LocationTrack.Edit> { trackId ->
|
post("/track/{id}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||||
list.firstOrNull()
|
list.firstOrNull()
|
||||||
}
|
}
|
||||||
val editTrack = Track.get(trackId.id)
|
var editTrack = TrackRepository.get(trackId) ?: return@post
|
||||||
|
|
||||||
params["name"]?.let { editTrack.name = it }
|
params["name"]?.let { editTrack = editTrack.copy(name = it) }
|
||||||
|
|
||||||
editTrack.color = (params["color"] ?: return@post).let { c ->
|
editTrack = editTrack.copy(color = (params["color"] ?: return@post).let { c ->
|
||||||
Color.default.find { it.first == c }
|
Color.default.find { it.first == c }
|
||||||
}?.second ?: (params["color-input"] ?: return@post).let(Color.Companion::parse)
|
}?.second ?: (params["color-input"] ?: return@post).let(Color.Companion::parse))
|
||||||
|
|
||||||
editTrack.save()
|
TrackRepository.update(editTrack)
|
||||||
|
|
||||||
call.respondRedirect("/track")
|
call.respondRedirect("/tracks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationTrack.New> {
|
get("/track/new") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -266,11 +255,8 @@ fun Route.track() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<LocationTrack.New> {
|
post("/track/new") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||||
list.firstOrNull()
|
list.firstOrNull()
|
||||||
}
|
}
|
||||||
|
@ -280,25 +266,21 @@ fun Route.track() {
|
||||||
Color.default.find { it.first == c }
|
Color.default.find { it.first == c }
|
||||||
}?.second ?: (params["color-input"] ?: return@post).let(Color.Companion::parse)
|
}?.second ?: (params["color-input"] ?: return@post).let(Color.Companion::parse)
|
||||||
|
|
||||||
Track(
|
val track = Track(null, name, color)
|
||||||
name = name,
|
|
||||||
color = color
|
|
||||||
).save()
|
|
||||||
|
|
||||||
call.respondRedirect("/track")
|
TrackRepository.create(track)
|
||||||
|
|
||||||
|
call.respondRedirect("/tracks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationTrack.Delete> { trackId ->
|
get("track/{id}/delete") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val deleteTrack = Track.get(trackId.id)
|
|
||||||
|
|
||||||
deleteTrack.delete()
|
TrackRepository.delete(trackId)
|
||||||
|
|
||||||
call.respondRedirect("/track")
|
call.respondRedirect("/tracks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,40 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.LocationLogin
|
|
||||||
import de.kif.backend.LocationUser
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.PortalSession
|
import de.kif.backend.hashPassword
|
||||||
import de.kif.backend.model.Permission
|
import de.kif.backend.repository.UserRepository
|
||||||
import de.kif.backend.model.User
|
|
||||||
import de.kif.backend.util.Search
|
import de.kif.backend.util.Search
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.User
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.html.insert
|
import io.ktor.html.insert
|
||||||
import io.ktor.html.respondHtmlTemplate
|
import io.ktor.html.respondHtmlTemplate
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.locations.post
|
|
||||||
import io.ktor.request.path
|
|
||||||
import io.ktor.request.receiveParameters
|
import io.ktor.request.receiveParameters
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.sessions.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.sessions.sessions
|
import io.ktor.routing.post
|
||||||
import io.ktor.util.toMap
|
import io.ktor.util.toMap
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
|
import kotlin.collections.filter
|
||||||
|
import kotlin.collections.firstOrNull
|
||||||
|
import kotlin.collections.joinToString
|
||||||
|
import kotlin.collections.mapNotNull
|
||||||
|
import kotlin.collections.mapValues
|
||||||
|
import kotlin.collections.set
|
||||||
|
import kotlin.collections.toSet
|
||||||
|
|
||||||
fun Route.user() {
|
fun Route.user() {
|
||||||
get<LocationUser> { param ->
|
get("/users") { param ->
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.USER) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
val search = call.parameters["search"] ?: ""
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val list = UserRepository.all()
|
||||||
} else {
|
|
||||||
val list = User.list()
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -38,11 +43,11 @@ fun Route.user() {
|
||||||
content {
|
content {
|
||||||
h1 { +"Users" }
|
h1 { +"Users" }
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = param.search
|
searchValue = search
|
||||||
|
|
||||||
action {
|
action {
|
||||||
a("/user/new") {
|
a("/user/new") {
|
||||||
button(classes="form-btn btn-primary") {
|
button(classes = "form-btn btn-primary") {
|
||||||
+"Add user"
|
+"Add user"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +66,7 @@ fun Route.user() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(param.search, u.username)) {
|
if (Search.match(search, u.username)) {
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.username)
|
attributes["data-search"] = Search.pack(u.username)
|
||||||
td {
|
td {
|
||||||
|
@ -84,12 +89,10 @@ fun Route.user() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationUser.Edit> { userId ->
|
get("/user/{id}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.USER) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
val userId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val editUser = UserRepository.get(userId) ?: return@get
|
||||||
} else {
|
|
||||||
val editUser = User.get(userId.id) ?: return@get
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -158,39 +161,31 @@ fun Route.user() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<LocationUser.Edit> { userId ->
|
post("/user/{id}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.USER) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
val userId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||||
list.firstOrNull()
|
list.firstOrNull()
|
||||||
}
|
}
|
||||||
val editUser = User.get(userId.id) ?: return@post
|
|
||||||
|
|
||||||
params["username"]?.let { editUser.username = it }
|
var editUser = UserRepository.get(userId) ?: return@post
|
||||||
|
|
||||||
for (permission in Permission.values()) {
|
params["username"]?.let { editUser = editUser.copy(username = it) }
|
||||||
|
|
||||||
|
val permissions = Permission.values().filter { permission ->
|
||||||
val name = permission.toString().toLowerCase()
|
val name = permission.toString().toLowerCase()
|
||||||
if (user.checkPermission(permission)) {
|
user.checkPermission(permission) && params["permission-$name"] == "on"
|
||||||
if (params["permission-$name"] == "on") {
|
}.toSet()
|
||||||
editUser.permissions += permission
|
editUser = editUser.copy(permissions = permissions)
|
||||||
} else {
|
|
||||||
editUser.permissions -= permission
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editUser.save()
|
|
||||||
|
|
||||||
call.respondRedirect("/user")
|
UserRepository.update(user)
|
||||||
|
|
||||||
|
call.respondRedirect("/users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationUser.New> {
|
get("/user/new") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.USER) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -269,11 +264,8 @@ fun Route.user() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<LocationUser.New> {
|
post("/user/new") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.USER) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||||
list.firstOrNull()
|
list.firstOrNull()
|
||||||
}
|
}
|
||||||
|
@ -288,26 +280,26 @@ fun Route.user() {
|
||||||
null
|
null
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
User.create(username, password, permissions)
|
val newUser = User(null, username, hashPassword(password), permissions)
|
||||||
|
|
||||||
call.respondRedirect("/user")
|
UserRepository.create(newUser)
|
||||||
|
|
||||||
|
call.respondRedirect("/users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationUser.Delete> { userId ->
|
get("/user/{id}/delete") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.USER) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
val userId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val deleteUser = UserRepository.get(userId) ?: return@get
|
||||||
} else {
|
|
||||||
val deleteUser = User.get(userId.id) ?: return@get
|
|
||||||
|
|
||||||
if (user.checkPermission(Permission.USER) &&
|
if (user.checkPermission(Permission.USER) &&
|
||||||
(Permission.ADMIN !in deleteUser.permissions || Permission.ADMIN in user.permissions)
|
(Permission.ADMIN !in deleteUser.permissions || Permission.ADMIN in user.permissions)
|
||||||
) {
|
) {
|
||||||
deleteUser.delete()
|
UserRepository.delete(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
call.respondRedirect("/user")
|
call.respondRedirect("/users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,38 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.route
|
||||||
|
|
||||||
import de.kif.backend.LocationLogin
|
|
||||||
import de.kif.backend.LocationWorkGroup
|
import de.kif.backend.authenticateOrRedirect
|
||||||
import de.kif.backend.PortalSession
|
import de.kif.backend.repository.TrackRepository
|
||||||
import de.kif.backend.database.Language
|
import de.kif.backend.repository.WorkGroupRepository
|
||||||
import de.kif.backend.model.Permission
|
|
||||||
import de.kif.backend.model.Track
|
|
||||||
import de.kif.backend.model.WorkGroup
|
|
||||||
import de.kif.backend.util.Search
|
import de.kif.backend.util.Search
|
||||||
import de.kif.backend.view.MainTemplate
|
import de.kif.backend.view.MainTemplate
|
||||||
import de.kif.backend.view.MenuTemplate
|
import de.kif.backend.view.MenuTemplate
|
||||||
import de.kif.backend.view.TableTemplate
|
import de.kif.backend.view.TableTemplate
|
||||||
|
import de.kif.common.model.Language
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.WorkGroup
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.html.insert
|
import io.ktor.html.insert
|
||||||
import io.ktor.html.respondHtmlTemplate
|
import io.ktor.html.respondHtmlTemplate
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.locations.post
|
|
||||||
import io.ktor.request.path
|
|
||||||
import io.ktor.request.receiveParameters
|
import io.ktor.request.receiveParameters
|
||||||
import io.ktor.response.respondRedirect
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.sessions.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.sessions.sessions
|
import io.ktor.routing.post
|
||||||
import io.ktor.util.toMap
|
import io.ktor.util.toMap
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
import kotlin.collections.component1
|
||||||
|
import kotlin.collections.component2
|
||||||
|
import kotlin.collections.find
|
||||||
|
import kotlin.collections.firstOrNull
|
||||||
|
import kotlin.collections.mapValues
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
fun Route.workGroup() {
|
fun Route.workGroup() {
|
||||||
get<LocationWorkGroup> { param ->
|
get("workgroups") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val search = call.parameters["search"] ?: ""
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val list = WorkGroupRepository.all()
|
||||||
} else {
|
|
||||||
val list = WorkGroup.list()
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -40,10 +41,10 @@ fun Route.workGroup() {
|
||||||
content {
|
content {
|
||||||
h1 { +"Work groups" }
|
h1 { +"Work groups" }
|
||||||
insert(TableTemplate()) {
|
insert(TableTemplate()) {
|
||||||
searchValue = param.search
|
searchValue = search
|
||||||
|
|
||||||
action {
|
action {
|
||||||
a("/track") {
|
a("/tracks") {
|
||||||
button(classes = "form-btn") {
|
button(classes = "form-btn") {
|
||||||
+"Edit tracks"
|
+"Edit tracks"
|
||||||
}
|
}
|
||||||
|
@ -83,7 +84,7 @@ fun Route.workGroup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u in list) {
|
for (u in list) {
|
||||||
if (Search.match(param.search, u.name)) {
|
if (Search.match(search, u.name)) {
|
||||||
entry {
|
entry {
|
||||||
attributes["data-search"] = Search.pack(u.name)
|
attributes["data-search"] = Search.pack(u.name)
|
||||||
td {
|
td {
|
||||||
|
@ -121,13 +122,11 @@ fun Route.workGroup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationWorkGroup.Edit> { workGroupId ->
|
get("/workgroup/{id}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val workGroupId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
val editWorkGroup = WorkGroupRepository.get(workGroupId) ?: return@get
|
||||||
} else {
|
val tracks = TrackRepository.all()
|
||||||
val editWorkGroup = WorkGroup.get(workGroupId.id)
|
|
||||||
val tracks = Track.list()
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -179,20 +178,20 @@ fun Route.workGroup() {
|
||||||
name = "track"
|
name = "track"
|
||||||
|
|
||||||
option {
|
option {
|
||||||
selected = (editWorkGroup.trackId ?: -1) < 0
|
selected = (editWorkGroup.track?.id ?: -1) < 0
|
||||||
value = "-1"
|
value = "-1"
|
||||||
+"None"
|
+"None"
|
||||||
}
|
}
|
||||||
for (track in tracks) {
|
for (track in tracks) {
|
||||||
option {
|
option {
|
||||||
selected = editWorkGroup.trackId == track.id
|
selected = editWorkGroup.track?.id == track.id
|
||||||
value = track.id.toString()
|
value = track.id.toString()
|
||||||
+track.name
|
+track.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a("/track", classes = "form-btn") {
|
a("/tracks", classes = "form-btn") {
|
||||||
i("material-icons") { +"edit" }
|
i("material-icons") { +"edit" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,8 +227,8 @@ fun Route.workGroup() {
|
||||||
for (language in Language.values()) {
|
for (language in Language.values()) {
|
||||||
option {
|
option {
|
||||||
selected = editWorkGroup.language == language
|
selected = editWorkGroup.language == language
|
||||||
value = language.name
|
value = language.code
|
||||||
+language.toString()
|
+language.localeName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,36 +286,37 @@ fun Route.workGroup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<LocationWorkGroup.Edit> { workGroupId ->
|
post("/workgroup/{id}") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val workGroupId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||||
list.firstOrNull()
|
list.firstOrNull()
|
||||||
}
|
}
|
||||||
val editWorkGroup = WorkGroup.get(workGroupId.id)
|
var editWorkGroup = WorkGroupRepository.get(workGroupId) ?: return@post
|
||||||
|
|
||||||
params["name"]?.let { editWorkGroup.name = it }
|
params["name"]?.let { editWorkGroup = editWorkGroup.copy(name = it) }
|
||||||
params["interested"]?.toIntOrNull()?.let { editWorkGroup.interested = it }
|
params["interested"]?.toIntOrNull()?.let { editWorkGroup = editWorkGroup.copy(interested = it) }
|
||||||
params["track"]?.toIntOrNull()?.let { editWorkGroup.trackId = it }
|
params["track"]?.toLongOrNull()?.let {
|
||||||
params["projector"]?.let { editWorkGroup.projector = it == "on" }
|
val track = TrackRepository.get(it)
|
||||||
params["resolution"]?.let { editWorkGroup.resolution = it == "on" }
|
editWorkGroup = editWorkGroup.copy(track = track)
|
||||||
params["length"]?.toIntOrNull()?.let { editWorkGroup.length = it }
|
}
|
||||||
params["language"]?.let { editWorkGroup.language = Language.values().find { l -> l.name == it } ?: Language.GERMAN }
|
params["projector"]?.let { editWorkGroup = editWorkGroup.copy(projector = it == "on") }
|
||||||
|
params["resolution"]?.let { editWorkGroup = editWorkGroup.copy(resolution = it == "on") }
|
||||||
|
params["length"]?.toIntOrNull()?.let { editWorkGroup = editWorkGroup.copy(length = it) }
|
||||||
|
params["language"]?.let {
|
||||||
|
editWorkGroup =
|
||||||
|
editWorkGroup.copy(language = Language.values().find { l -> l.code == it } ?: Language.GERMAN)
|
||||||
|
}
|
||||||
|
|
||||||
editWorkGroup.save()
|
WorkGroupRepository.update(editWorkGroup)
|
||||||
|
|
||||||
call.respondRedirect("/workgroup")
|
call.respondRedirect("/workgroups")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationWorkGroup.New> {
|
get("/workgroup/new") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val tracks = TrackRepository.all()
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val tracks = Track.list()
|
|
||||||
call.respondHtmlTemplate(MainTemplate()) {
|
call.respondHtmlTemplate(MainTemplate()) {
|
||||||
menuTemplate {
|
menuTemplate {
|
||||||
this.user = user
|
this.user = user
|
||||||
|
@ -410,8 +410,8 @@ fun Route.workGroup() {
|
||||||
for (language in Language.values()) {
|
for (language in Language.values()) {
|
||||||
option {
|
option {
|
||||||
selected = language == Language.GERMAN
|
selected = language == Language.GERMAN
|
||||||
value = language.name
|
value = language.code
|
||||||
+language.toString()
|
+language.localeName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,47 +464,46 @@ fun Route.workGroup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<LocationWorkGroup.New> {
|
post("/workgroup/new") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||||
list.firstOrNull()
|
list.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
val name = params["name"] ?: return@post
|
val name = params["name"] ?: return@post
|
||||||
val interested = (params["interested"] ?: return@post).toIntOrNull() ?: 0
|
val interested = (params["interested"] ?: return@post).toIntOrNull() ?: 0
|
||||||
val trackId = (params["track"] ?: return@post).toIntOrNull()
|
val track = (params["track"] ?: return@post).toLongOrNull()?.let { TrackRepository.get(it) }
|
||||||
val projector = params["projector"] == "on"
|
val projector = params["projector"] == "on"
|
||||||
val resolution = params["resolution"] == "on"
|
val resolution = params["resolution"] == "on"
|
||||||
val length = (params["length"] ?: return@post).toIntOrNull() ?: 0
|
val length = (params["length"] ?: return@post).toIntOrNull() ?: 0
|
||||||
val language = (params["language"] ?: return@post).let { Language.values().find { l -> l.name == it } ?: Language.GERMAN }
|
val language = (params["language"] ?: return@post).let {
|
||||||
|
Language.values().find { l -> l.code == it } ?: Language.GERMAN
|
||||||
|
}
|
||||||
|
|
||||||
WorkGroup(
|
val workGroup = WorkGroup(
|
||||||
|
null,
|
||||||
name = name,
|
name = name,
|
||||||
interested = interested,
|
interested = interested,
|
||||||
trackId = trackId,
|
track = track,
|
||||||
projector = projector,
|
projector = projector,
|
||||||
resolution = resolution,
|
resolution = resolution,
|
||||||
length = length,
|
length = length,
|
||||||
language = language
|
language = language
|
||||||
).save()
|
)
|
||||||
|
|
||||||
call.respondRedirect("/workgroup")
|
WorkGroupRepository.create(workGroup)
|
||||||
|
|
||||||
|
call.respondRedirect("/workgroups")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get<LocationWorkGroup.Delete> { workGroupId ->
|
get("/workgroup/{id}/delete") {
|
||||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
val workGroupId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
|
||||||
} else {
|
|
||||||
val deleteWorkGroup = WorkGroup.get(workGroupId.id)
|
|
||||||
|
|
||||||
deleteWorkGroup.delete()
|
WorkGroupRepository.delete(workGroupId)
|
||||||
|
|
||||||
call.respondRedirect("/workgroup")
|
call.respondRedirect("/workgroups")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
61
src/jvmMain/kotlin/de/kif/backend/route/api/Authenticate.kt
Normal file
61
src/jvmMain/kotlin/de/kif/backend/route/api/Authenticate.kt
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package de.kif.backend.route.api
|
||||||
|
|
||||||
|
import de.kif.backend.PortalSession
|
||||||
|
import de.kif.backend.checkPassword
|
||||||
|
import de.kif.backend.repository.UserRepository
|
||||||
|
import io.ktor.application.ApplicationCall
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.routing.post
|
||||||
|
import io.ktor.sessions.sessions
|
||||||
|
import io.ktor.sessions.set
|
||||||
|
|
||||||
|
data class Credentials(
|
||||||
|
val username: String,
|
||||||
|
val password: String
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Route.authenticateApi() {
|
||||||
|
post("/api/authenticate") {
|
||||||
|
val credentials = call.receive<Credentials>()
|
||||||
|
|
||||||
|
val user = UserRepository.find(credentials.username)
|
||||||
|
|
||||||
|
if (user?.id == null || !checkPassword(credentials.password, user.password)) {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
call.sessions.set(PortalSession(user.id))
|
||||||
|
call.respond(HttpStatusCode.OK, mapOf("OK" to true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun ApplicationCall.success(data: Any? = null) {
|
||||||
|
val map: Map<String, Any>
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
map = mapOf("OK" to true)
|
||||||
|
} else {
|
||||||
|
map = mapOf(
|
||||||
|
"OK" to true,
|
||||||
|
"data" to data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
respond(HttpStatusCode.OK, map)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun ApplicationCall.error(code: HttpStatusCode) {
|
||||||
|
respond(
|
||||||
|
code,
|
||||||
|
mapOf(
|
||||||
|
"OK" to false,
|
||||||
|
"errorCode" to code.value,
|
||||||
|
"errorDescription" to code.description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
92
src/jvmMain/kotlin/de/kif/backend/route/api/Room.kt
Normal file
92
src/jvmMain/kotlin/de/kif/backend/route/api/Room.kt
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package de.kif.backend.route.api
|
||||||
|
|
||||||
|
import de.kif.backend.authenticate
|
||||||
|
import de.kif.backend.repository.RoomRepository
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.Room
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.routing.get
|
||||||
|
import io.ktor.routing.post
|
||||||
|
|
||||||
|
fun Route.roomApi() {
|
||||||
|
get("/api/rooms") {
|
||||||
|
try {
|
||||||
|
val rooms = RoomRepository.all()
|
||||||
|
call.success(rooms)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/rooms") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.ROOM) {
|
||||||
|
val room = call.receive<Room>()
|
||||||
|
|
||||||
|
val id = RoomRepository.create(room)
|
||||||
|
|
||||||
|
call.success(mapOf("id" to id))
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/api/room/{id}") {
|
||||||
|
try {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val room = id?.let { RoomRepository.get(it) }
|
||||||
|
|
||||||
|
if (room != null) {
|
||||||
|
call.success(room)
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/room/{id}") {
|
||||||
|
try {
|
||||||
|
authenticate {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val room = call.receive<Room>().copy(id = id)
|
||||||
|
|
||||||
|
if (room.id != null) {
|
||||||
|
RoomRepository.update(room)
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("/api/room/{id}/delete") {
|
||||||
|
try {
|
||||||
|
authenticate {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
RoomRepository.delete(id)
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
src/jvmMain/kotlin/de/kif/backend/route/api/Schedule.kt
Normal file
121
src/jvmMain/kotlin/de/kif/backend/route/api/Schedule.kt
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package de.kif.backend.route.api
|
||||||
|
|
||||||
|
import de.kif.backend.authenticate
|
||||||
|
import de.kif.backend.repository.RoomRepository
|
||||||
|
import de.kif.backend.repository.ScheduleRepository
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.Schedule
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.routing.get
|
||||||
|
import io.ktor.routing.post
|
||||||
|
|
||||||
|
data class ScheduleMove(val day: Int, val time: Int, val roomId: Long)
|
||||||
|
|
||||||
|
fun Route.scheduleApi() {
|
||||||
|
get("/api/schedules") {
|
||||||
|
try {
|
||||||
|
val schedules = ScheduleRepository.all()
|
||||||
|
call.success(schedules)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/schedules") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.SCHEDULE) {
|
||||||
|
val schedule = call.receive<Schedule>()
|
||||||
|
|
||||||
|
val id = ScheduleRepository.create(schedule)
|
||||||
|
|
||||||
|
call.success(mapOf("id" to id))
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/api/schedule/{id}") {
|
||||||
|
try {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val schedule = id?.let { ScheduleRepository.get(it) }
|
||||||
|
|
||||||
|
if (schedule != null) {
|
||||||
|
call.success(schedule)
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/schedule/{id}") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.SCHEDULE) {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val schedule = call.receive<Schedule>().copy(id = id)
|
||||||
|
|
||||||
|
if (schedule.id != null) {
|
||||||
|
ScheduleRepository.update(schedule)
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/schedule/{id}/delete") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.SCHEDULE) {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
ScheduleRepository.delete(id)
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/schedule/{id}/move") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.SCHEDULE) {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val scheduleMove = call.receive<ScheduleMove>()
|
||||||
|
var schedule = id?.let { ScheduleRepository.get(it) }
|
||||||
|
val room = RoomRepository.get(scheduleMove.roomId)
|
||||||
|
|
||||||
|
if (schedule != null && room != null) {
|
||||||
|
schedule = schedule.copy(day = scheduleMove.day, time = scheduleMove.time, room = room)
|
||||||
|
|
||||||
|
ScheduleRepository.update(schedule)
|
||||||
|
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
src/jvmMain/kotlin/de/kif/backend/route/api/Track.kt
Normal file
92
src/jvmMain/kotlin/de/kif/backend/route/api/Track.kt
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package de.kif.backend.route.api
|
||||||
|
|
||||||
|
import de.kif.backend.authenticate
|
||||||
|
import de.kif.backend.repository.TrackRepository
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.Track
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.routing.get
|
||||||
|
import io.ktor.routing.post
|
||||||
|
|
||||||
|
fun Route.trackApi() {
|
||||||
|
get("/api/tracks") {
|
||||||
|
try {
|
||||||
|
val tracks = TrackRepository.all()
|
||||||
|
call.success(tracks)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/tracks") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.WORK_GROUP) {
|
||||||
|
val track = call.receive<Track>()
|
||||||
|
|
||||||
|
val id = TrackRepository.create(track)
|
||||||
|
|
||||||
|
call.success(mapOf("id" to id))
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/api/track/{id}") {
|
||||||
|
try {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val track = id?.let { TrackRepository.get(it) }
|
||||||
|
|
||||||
|
if (track != null) {
|
||||||
|
TrackRepository.update(track)
|
||||||
|
call.success(track)
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/track/{id}") {
|
||||||
|
try {
|
||||||
|
authenticate {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val track = call.receive<Track>().copy(id = id)
|
||||||
|
|
||||||
|
if (track.id != null) {
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("/api/track/{id}/delete") {
|
||||||
|
try {
|
||||||
|
authenticate {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
TrackRepository.delete(id)
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
src/jvmMain/kotlin/de/kif/backend/route/api/User.kt
Normal file
99
src/jvmMain/kotlin/de/kif/backend/route/api/User.kt
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package de.kif.backend.route.api
|
||||||
|
|
||||||
|
import de.kif.backend.authenticate
|
||||||
|
import de.kif.backend.repository.UserRepository
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.User
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.routing.get
|
||||||
|
import io.ktor.routing.post
|
||||||
|
|
||||||
|
fun Route.userApi() {
|
||||||
|
get("/api/users") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.USER) {
|
||||||
|
val users = UserRepository.all()
|
||||||
|
call.success(users)
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/users") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.USER) {
|
||||||
|
val user = call.receive<User>()
|
||||||
|
|
||||||
|
val id = UserRepository.create(user)
|
||||||
|
|
||||||
|
call.success(mapOf("id" to id))
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/api/user/{id}") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.USER) {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val user = id?.let { UserRepository.get(it) }
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
call.success(user)
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/user/{id}") {
|
||||||
|
try {
|
||||||
|
authenticate {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val user = call.receive<User>().copy(id = id)
|
||||||
|
|
||||||
|
if (user.id != null) {
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("/api/user/{id}/delete") {
|
||||||
|
try {
|
||||||
|
authenticate {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
UserRepository.delete(id)
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
src/jvmMain/kotlin/de/kif/backend/route/api/WorkGroup.kt
Normal file
92
src/jvmMain/kotlin/de/kif/backend/route/api/WorkGroup.kt
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package de.kif.backend.route.api
|
||||||
|
|
||||||
|
import de.kif.backend.authenticate
|
||||||
|
import de.kif.backend.repository.WorkGroupRepository
|
||||||
|
import de.kif.common.model.Permission
|
||||||
|
import de.kif.common.model.WorkGroup
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.routing.get
|
||||||
|
import io.ktor.routing.post
|
||||||
|
|
||||||
|
fun Route.workGroupApi() {
|
||||||
|
get("/api/workgroups") {
|
||||||
|
try {
|
||||||
|
val workGroups = WorkGroupRepository.all()
|
||||||
|
call.success(workGroups)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/workgroups") {
|
||||||
|
try {
|
||||||
|
authenticate(Permission.WORK_GROUP) {
|
||||||
|
val workGroup = call.receive<WorkGroup>()
|
||||||
|
|
||||||
|
val id = WorkGroupRepository.create(workGroup)
|
||||||
|
|
||||||
|
call.success(mapOf("id" to id))
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/api/workgroup/{id}") {
|
||||||
|
try {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val workGroup = id?.let { WorkGroupRepository.get(it) }
|
||||||
|
|
||||||
|
if (workGroup != null) {
|
||||||
|
call.success(workGroup)
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/workgroup/{id}") {
|
||||||
|
try {
|
||||||
|
authenticate {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
val workGroup = call.receive<WorkGroup>().copy(id = id)
|
||||||
|
|
||||||
|
if (workGroup.id != null) {
|
||||||
|
WorkGroupRepository.update(workGroup)
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post("/api/workgroup/{id}/delete") {
|
||||||
|
try {
|
||||||
|
authenticate {
|
||||||
|
val id = call.parameters["id"]?.toLongOrNull()
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
WorkGroupRepository.delete(id)
|
||||||
|
call.success()
|
||||||
|
} else {
|
||||||
|
call.error(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
} onFailure {
|
||||||
|
call.error(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
call.error(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
package de.kif.backend.route
|
package de.kif.backend.util
|
||||||
|
|
||||||
|
import de.kif.backend.repository.*
|
||||||
|
import de.kif.common.*
|
||||||
|
import de.kif.common.RepositoryType
|
||||||
import io.ktor.http.cio.websocket.Frame
|
import io.ktor.http.cio.websocket.Frame
|
||||||
import io.ktor.http.cio.websocket.readText
|
import io.ktor.http.cio.websocket.readText
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.websocket.WebSocketServerSession
|
import io.ktor.websocket.WebSocketServerSession
|
||||||
import io.ktor.websocket.webSocket
|
import io.ktor.websocket.webSocket
|
||||||
import kif.common.model.Message
|
|
||||||
import kif.common.model.MessageType
|
|
||||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
object PushService {
|
object PushService {
|
||||||
|
|
||||||
var clients: List<WebSocketServerSession> = emptyList()
|
var clients: List<WebSocketServerSession> = emptyList()
|
||||||
|
|
||||||
suspend fun notify(messageType: MessageType) {
|
suspend fun notify(type: MessageType, repository: RepositoryType, id: Long) {
|
||||||
try {
|
try {
|
||||||
val data = Message(messageType).stringify()
|
val data = Message(type, repository, id).stringify()
|
||||||
for (client in clients) {
|
for (client in clients) {
|
||||||
client.outgoing.send(Frame.Text(data))
|
client.outgoing.send(Frame.Text(data))
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,15 @@ fun Route.pushService() {
|
||||||
} catch (_: ClosedReceiveChannelException) {
|
} catch (_: ClosedReceiveChannelException) {
|
||||||
PushService.clients -= this
|
PushService.clients -= this
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
println("onError ${closeReason.await()}")
|
println("onFailure ${closeReason.await()}")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
PushService.clients -= this
|
PushService.clients -= this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RoomRepository.registerPushService()
|
||||||
|
ScheduleRepository.registerPushService()
|
||||||
|
TrackRepository.registerPushService()
|
||||||
|
UserRepository.registerPushService()
|
||||||
|
WorkGroupRepository.registerPushService()
|
||||||
}
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
package de.kif.backend.view
|
package de.kif.backend.view
|
||||||
|
|
||||||
import de.kif.backend.model.Permission
|
import de.kif.common.model.Permission
|
||||||
import de.kif.backend.model.User
|
import de.kif.common.model.User
|
||||||
import io.ktor.html.Template
|
import io.ktor.html.Template
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
class MenuTemplate() : Template<FlowContent> {
|
class MenuTemplate() : Template<FlowContent> {
|
||||||
|
|
||||||
var setup = false
|
|
||||||
var active: Tab = Tab.DASHBOARD
|
var active: Tab = Tab.DASHBOARD
|
||||||
var user: User? = null
|
var user: User? = null
|
||||||
|
|
||||||
|
@ -15,11 +14,6 @@ class MenuTemplate() : Template<FlowContent> {
|
||||||
nav("menu") {
|
nav("menu") {
|
||||||
div("container") {
|
div("container") {
|
||||||
div("menu-left") {
|
div("menu-left") {
|
||||||
if (setup) {
|
|
||||||
a(classes = "active") {
|
|
||||||
+"Setup"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
a("/", classes = if (active == Tab.DASHBOARD) "active" else null) {
|
a("/", classes = if (active == Tab.DASHBOARD) "active" else null) {
|
||||||
+"Dashboard"
|
+"Dashboard"
|
||||||
}
|
}
|
||||||
|
@ -27,8 +21,6 @@ class MenuTemplate() : Template<FlowContent> {
|
||||||
+"Calendar"
|
+"Calendar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!setup) {
|
|
||||||
div("menu-right") {
|
div("menu-right") {
|
||||||
span("menu-icon") {
|
span("menu-icon") {
|
||||||
i("material-icons") { +"menu" }
|
i("material-icons") { +"menu" }
|
||||||
|
@ -41,22 +33,17 @@ class MenuTemplate() : Template<FlowContent> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (user.checkPermission(Permission.WORK_GROUP)) {
|
if (user.checkPermission(Permission.WORK_GROUP)) {
|
||||||
a("/workgroup", classes = if (active == Tab.WORK_GROUP) "active" else null) {
|
a("/workgroups", classes = if (active == Tab.WORK_GROUP) "active" else null) {
|
||||||
+"Work groups"
|
+"Work groups"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user.checkPermission(Permission.ROOM)) {
|
if (user.checkPermission(Permission.ROOM)) {
|
||||||
a("/room", classes = if (active == Tab.ROOM) "active" else null) {
|
a("/rooms", classes = if (active == Tab.ROOM) "active" else null) {
|
||||||
+"Rooms"
|
+"Rooms"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user.checkPermission(Permission.PERSON)) {
|
if (user.checkPermission(Permission.PERSON)) {
|
||||||
a("/person", classes = if (active == Tab.PERSON) "active" else null) {
|
a("/users", classes = if (active == Tab.USER) "active" else null) {
|
||||||
+"Persons"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (user.checkPermission(Permission.PERSON)) {
|
|
||||||
a("/user", classes = if (active == Tab.USER) "active" else null) {
|
|
||||||
+"Users"
|
+"Users"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +56,6 @@ class MenuTemplate() : Template<FlowContent> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
enum class Tab {
|
enum class Tab {
|
||||||
DASHBOARD, CALENDAR, ACCOUNT, WORK_GROUP, ROOM, PERSON, USER
|
DASHBOARD, CALENDAR, ACCOUNT, WORK_GROUP, ROOM, PERSON, USER
|
||||||
|
|
38
src/jvmTest/resources/test.http
Normal file
38
src/jvmTest/resources/test.http
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
GET http://localhost:8080/api/rooms
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:8080/api/authenticate
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.assert(response.body.OK === true, "Login failed!");
|
||||||
|
client.global.set("auth_token", response.headers.valueOf("Set-Cookie"));
|
||||||
|
%}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:8080/api/rooms
|
||||||
|
Content-Type: application/json
|
||||||
|
Cookie: {{auth_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "E007",
|
||||||
|
"places": 20,
|
||||||
|
"projector": true
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
POST http://localhost:8080/api/room/2/delete
|
||||||
|
Content-Type: application/json
|
||||||
|
Cookie: {{auth_token}}
|
||||||
|
|
||||||
|
###
|
Loading…
Reference in a new issue