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/
|
||||
build/
|
||||
web/
|
||||
.sessions/
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
|
|
|
@ -35,7 +35,6 @@ kotlin {
|
|||
compilations.all {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += [
|
||||
"-Xuse-experimental=io.ktor.locations.KtorExperimentalLocationsAPI",
|
||||
"-Xuse-experimental=io.ktor.util.KtorExperimentalAPI"
|
||||
]
|
||||
}
|
||||
|
@ -54,6 +53,7 @@ kotlin {
|
|||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib-common')
|
||||
implementation "de.westermann:KObserve-metadata:0.9.1"
|
||||
|
||||
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-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-jackson:$ktor_version"
|
||||
implementation 'org.jetbrains:kotlin-css-jvm:1.0.0-pre.70-kotlin-1.3.21'
|
||||
|
||||
implementation "io.ktor:ktor-html-builder:$ktor_version"
|
||||
|
@ -83,6 +84,8 @@ kotlin {
|
|||
|
||||
implementation 'org.mindrot:jbcrypt:0.4'
|
||||
|
||||
implementation "de.westermann:KObserve-jvm:0.9.1"
|
||||
|
||||
api 'io.github.microutils:kotlin-logging:1.6.23'
|
||||
api 'ch.qos.logback:logback-classic:1.2.3'
|
||||
api 'org.fusesource.jansi:jansi:1.8'
|
||||
|
@ -155,10 +158,12 @@ task run(type: JavaExec, dependsOn: [jvmMainClasses, jsJar]) {
|
|||
]
|
||||
}
|
||||
args = []
|
||||
standardInput = System.in
|
||||
}
|
||||
|
||||
clean.doFirst {
|
||||
delete webFolder
|
||||
delete ".sessions"
|
||||
}
|
||||
|
||||
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
|
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.Transient
|
||||
|
||||
@Serializable
|
||||
data class Color(
|
||||
|
@ -21,25 +20,15 @@ data class Color(
|
|||
}
|
||||
}
|
||||
|
||||
@Transient
|
||||
val redDouble
|
||||
get() = red / 255.0
|
||||
fun calcRedDouble() = red / 255.0
|
||||
|
||||
@Transient
|
||||
val greenDouble
|
||||
get() = green / 255.0
|
||||
fun calcGreenDouble() = green / 255.0
|
||||
|
||||
@Transient
|
||||
val blueDouble
|
||||
get() = blue / 255.0
|
||||
fun calcBlueDouble() = blue / 255.0
|
||||
|
||||
@Transient
|
||||
val luminance: Double
|
||||
get() = 0.2126 * redDouble + 0.7152 * greenDouble + 0.0722 * blueDouble
|
||||
fun calcLuminance(): Double = 0.2126 * calcRedDouble() + 0.7152 * calcGreenDouble() + 0.0722 * calcBlueDouble()
|
||||
|
||||
@Transient
|
||||
val textColor: Color
|
||||
get() = if (luminance < 0.7) WHITE else BLACK
|
||||
fun calcTextColor(): Color = if (calcLuminance() < 0.7) WHITE else BLACK
|
||||
|
||||
companion object {
|
||||
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 {
|
||||
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
|
||||
|
||||
import de.westermann.kobserve.property.mapBinding
|
||||
import de.westermann.kwebview.*
|
||||
import de.westermann.kwebview.components.Body
|
||||
import de.kif.frontend.views.initCalendar
|
||||
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.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 {
|
||||
WebSocketClient()
|
||||
|
||||
val ws = WebSocket("ws://${window.location.host}/".also { println(it) })
|
||||
|
||||
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 -> {
|
||||
}
|
||||
}
|
||||
if (document.getElementsByClassName("calendar").length > 0) {
|
||||
initCalendar()
|
||||
}
|
||||
|
||||
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.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.property.property
|
||||
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) {
|
||||
if (clazz !in bound) {
|
||||
throw IllegalArgumentException("Class is not bound!")
|
||||
|
|
|
@ -7,10 +7,10 @@ import kotlin.math.min
|
|||
* @author lars
|
||||
*/
|
||||
data class Dimension(
|
||||
val left: Double,
|
||||
val top: Double,
|
||||
val width: Double = 0.0,
|
||||
val height: Double = 0.0
|
||||
val left: Double,
|
||||
val top: Double,
|
||||
val width: Double = 0.0,
|
||||
val height: Double = 0.0
|
||||
) {
|
||||
|
||||
constructor(position: Point, size: Point = Point.ZERO) : this(position.x, position.y, size.x, size.y)
|
||||
|
@ -27,12 +27,15 @@ data class Dimension(
|
|||
val bottom: Double
|
||||
get() = top + height
|
||||
|
||||
val center: Point
|
||||
get() = Point(left + width / 2.0, top + height / 2.0)
|
||||
|
||||
val edges: Set<Point>
|
||||
get() = setOf(
|
||||
Point(left, top),
|
||||
Point(right, top),
|
||||
Point(left, bottom),
|
||||
Point(right, bottom)
|
||||
Point(left, top),
|
||||
Point(right, top),
|
||||
Point(left, bottom),
|
||||
Point(right, bottom)
|
||||
)
|
||||
|
||||
val normalized: Dimension
|
||||
|
|
|
@ -110,7 +110,7 @@ fun get(
|
|||
}
|
||||
}
|
||||
|
||||
fun post(
|
||||
fun postForm(
|
||||
url: String,
|
||||
data: Map<String, String> = emptyMap(),
|
||||
onError: (Int) -> Unit = {},
|
||||
|
@ -141,3 +141,43 @@ fun post(
|
|||
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,203 +1,60 @@
|
|||
package de.kif.backend
|
||||
|
||||
import de.kif.backend.database.Connection
|
||||
import de.kif.backend.model.User
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
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.ApplicationCall
|
||||
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.http.content.files
|
||||
import io.ktor.http.content.static
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.Locations
|
||||
import io.ktor.response.respondRedirect
|
||||
import io.ktor.jackson.jackson
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.sessions.*
|
||||
import io.ktor.util.hex
|
||||
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() {
|
||||
Connection.init()
|
||||
|
||||
install(DefaultHeaders)
|
||||
install(CallLogging)
|
||||
install(ConditionalHeaders)
|
||||
install(Compression)
|
||||
install(DataConversion)
|
||||
install(Locations)
|
||||
install(WebSockets)
|
||||
|
||||
install(Authentication) {
|
||||
form {
|
||||
userParamName = LocationLogin::username.name
|
||||
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
|
||||
}
|
||||
install(ContentNegotiation) {
|
||||
jackson {
|
||||
enable(SerializationFeature.INDENT_OUTPUT) // Pretty Prints the JSON
|
||||
}
|
||||
}
|
||||
|
||||
val sessionKey = hex("1234567890abcdef") //TODO
|
||||
install(Sessions) {
|
||||
cookie<PortalSession>("SESSION") {
|
||||
transform(SessionTransportTransformerMessageAuthentication(sessionKey))
|
||||
}
|
||||
}
|
||||
security()
|
||||
|
||||
routing {
|
||||
static("/static") {
|
||||
files(Resources.directory)
|
||||
}
|
||||
|
||||
launch {
|
||||
val firstStart = User.exists()
|
||||
if (firstStart) {
|
||||
log.info("Please create the first user and restart the server!")
|
||||
setup()
|
||||
} else {
|
||||
dashboard()
|
||||
calendar()
|
||||
login()
|
||||
account()
|
||||
// UI routes
|
||||
dashboard()
|
||||
calendar()
|
||||
login()
|
||||
account()
|
||||
|
||||
workGroup()
|
||||
track()
|
||||
room()
|
||||
person()
|
||||
user()
|
||||
workGroup()
|
||||
track()
|
||||
room()
|
||||
user()
|
||||
|
||||
pushService()
|
||||
}
|
||||
}
|
||||
// RESTful routes
|
||||
authenticateApi()
|
||||
|
||||
roomApi()
|
||||
scheduleApi()
|
||||
trackApi()
|
||||
userApi()
|
||||
workGroupApi()
|
||||
|
||||
// Web socket push notifications
|
||||
pushService()
|
||||
}
|
||||
}
|
|
@ -1,13 +1,50 @@
|
|||
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.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object Main {
|
||||
@Suppress("UnusedMainParameter")
|
||||
@JvmStatic
|
||||
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(
|
||||
factory = Netty,
|
||||
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 {
|
||||
SchemaUtils.create(
|
||||
DbPerson, DbPersonConstraint,
|
||||
DbTrack, DbWorkGroup, DbWorkGroupConstraint,
|
||||
DbLeader, DbWorkGroupOrder,
|
||||
DbTrack, DbWorkGroup,
|
||||
DbRoom, DbSchedule,
|
||||
DbUser, DbUserPermission
|
||||
)
|
||||
|
|
|
@ -1,71 +1,30 @@
|
|||
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
|
||||
|
||||
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() {
|
||||
val id = integer("id").autoIncrement().primaryKey()
|
||||
val id = long("id").autoIncrement().primaryKey()
|
||||
val name = varchar("name", 64)
|
||||
val color = varchar("color", 32)
|
||||
}
|
||||
|
||||
object DbWorkGroup : Table() {
|
||||
val id = integer("id").autoIncrement().primaryKey()
|
||||
val id = long("id").autoIncrement().primaryKey()
|
||||
val name = varchar("first_name", 64)
|
||||
|
||||
val interested = integer("interested")
|
||||
val trackId = integer("track_id").nullable()
|
||||
val trackId = long("track_id").nullable()
|
||||
val projector = bool("projector")
|
||||
val resolution = bool("resolution")
|
||||
val language = enumeration("language", Language::class)
|
||||
|
||||
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() {
|
||||
val id = integer("id").autoIncrement().primaryKey()
|
||||
val id = long("id").autoIncrement().primaryKey()
|
||||
val name = varchar("name", 64)
|
||||
|
||||
val places = integer("places")
|
||||
|
@ -73,30 +32,20 @@ object DbRoom : Table() {
|
|||
}
|
||||
|
||||
object DbSchedule : Table() {
|
||||
val workGroupId = integer("work_group_id").primaryKey(0)
|
||||
val day = integer("day").primaryKey(1)
|
||||
val time = integer("time_slot").primaryKey(2)
|
||||
val roomId = integer("room_id").primaryKey(3)
|
||||
}
|
||||
|
||||
enum class DbConstraintType {
|
||||
BEGIN, END, BLOCKED
|
||||
val id = long("id").autoIncrement().primaryKey()
|
||||
val workGroupId = long("work_group_id").index()
|
||||
val roomId = long("room_id").index()
|
||||
val day = integer("day").index()
|
||||
val time = integer("time_slot")
|
||||
}
|
||||
|
||||
object DbUser : Table() {
|
||||
val userId = integer("id").autoIncrement().primaryKey()
|
||||
val id = long("id").autoIncrement().primaryKey()
|
||||
val username = varchar("username", 64).uniqueIndex()
|
||||
val password = varchar("password", 64)
|
||||
}
|
||||
|
||||
object DbUserPermission : Table() {
|
||||
val userId = integer("id").primaryKey(0)
|
||||
val userId = long("id").primaryKey(0)
|
||||
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
|
||||
|
||||
import de.kif.backend.LocationAccount
|
||||
import de.kif.backend.LocationLogin
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.view.MainTemplate
|
||||
import de.kif.backend.view.MenuTemplate
|
||||
import io.ktor.application.call
|
||||
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.sessions.get
|
||||
import io.ktor.sessions.sessions
|
||||
import io.ktor.routing.get
|
||||
import kotlinx.html.*
|
||||
|
||||
fun Route.account() {
|
||||
get<LocationAccount> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
get("/account") {
|
||||
authenticateOrRedirect { user ->
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -34,7 +25,7 @@ fun Route.account() {
|
|||
+"You have the following rights: ${user.permissions}"
|
||||
br {}
|
||||
a("/logout") {
|
||||
button(classes="form-btn") {
|
||||
button(classes = "form-btn") {
|
||||
+"Logout"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +1,38 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.LocationCalendar
|
||||
import de.kif.backend.LocationLogin
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.model.Permission
|
||||
import de.kif.backend.model.Room
|
||||
import de.kif.backend.model.Schedule
|
||||
import de.kif.backend.model.WorkGroup
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.isAuthenticated
|
||||
import de.kif.backend.repository.RoomRepository
|
||||
import de.kif.backend.repository.ScheduleRepository
|
||||
import de.kif.backend.repository.WorkGroupRepository
|
||||
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 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.html.insert
|
||||
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.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.Color
|
||||
import kotlinx.css.pct
|
||||
import kotlinx.css.rem
|
||||
import kotlinx.html.*
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
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) {
|
||||
span("calendar-entry") {
|
||||
attributes["style"] = CSSBuilder().apply {
|
||||
|
@ -50,42 +48,38 @@ private fun DIV.calendarCell(schedule: Schedule?, day: Int, time: Int) {
|
|||
val c = schedule.workGroup.track?.color
|
||||
if (c != null) {
|
||||
backgroundColor = Color(c.toString())
|
||||
color = Color(c.textColor.toString())
|
||||
color = Color(c.calcTextColor().toString())
|
||||
}
|
||||
}.toString()
|
||||
attributes["data-language"] = schedule.workGroup.language.code
|
||||
attributes["data-day"] = schedule.day.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()
|
||||
attributes["data-id"] = schedule.id.toString()
|
||||
|
||||
+schedule.workGroup.name
|
||||
|
||||
div("calendar-tools") {
|
||||
a(
|
||||
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" }
|
||||
a(
|
||||
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" }
|
||||
a(
|
||||
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" }
|
||||
a(
|
||||
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" }
|
||||
a(
|
||||
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" }
|
||||
a(
|
||||
classes = "calendar-tools-del",
|
||||
href = "/calendar/$day/${schedule.room.id}/${schedule.time}/-1"
|
||||
href = "/calendar/${schedule.day}/${schedule.id}/delete"
|
||||
) { +"del" }
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +142,7 @@ private fun DIV.renderTimeToRoom(
|
|||
|
||||
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
|
||||
a(href, classes = "calendar-link")
|
||||
|
@ -216,7 +210,7 @@ private fun DIV.renderRoomToTime(
|
|||
|
||||
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
|
||||
a(href, classes = "calendar-link")
|
||||
|
@ -233,37 +227,40 @@ fun Route.calendar() {
|
|||
call.respondRedirect("/calendar/0", true)
|
||||
}
|
||||
|
||||
get<LocationCalendar.LocationCalendarRoomToTime> { param ->
|
||||
get("/calendar/{day}/rtt") {
|
||||
call.response.cookies.append(
|
||||
"orientation",
|
||||
CalendarOrientation.ROOM_TO_TIME.name,
|
||||
maxAge = Int.MAX_VALUE,
|
||||
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(
|
||||
"orientation",
|
||||
CalendarOrientation.TIME_TO_ROOM.name,
|
||||
maxAge = Int.MAX_VALUE,
|
||||
path = "/"
|
||||
)
|
||||
call.respondRedirect("/calendar/${param.day}")
|
||||
val day = call.parameters["day"]?.toIntOrNull() ?: 0
|
||||
call.respondRedirect("/calendar/$day")
|
||||
}
|
||||
|
||||
get<LocationCalendar> { param ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
val allowEdit = user?.checkPermission(Permission.SCHEDULE) ?: false
|
||||
val rooms = Room.list()
|
||||
get("/calendar/{day}") {
|
||||
val user = isAuthenticated(Permission.SCHEDULE)
|
||||
|
||||
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
|
||||
|
||||
val rooms = RoomRepository.all()
|
||||
|
||||
val orientation = call.request.cookies["orientation"]?.let { name ->
|
||||
CalendarOrientation.values().find { it.name == name }
|
||||
} ?: CalendarOrientation.ROOM_TO_TIME
|
||||
|
||||
val day = param.day
|
||||
val h = Schedule.getByDay(day)
|
||||
val h = ScheduleRepository.getByDay(day)
|
||||
val schedules = h.groupBy { it.room }.mapValues { (_, it) ->
|
||||
it.associateBy {
|
||||
it.time
|
||||
|
@ -327,7 +324,7 @@ fun Route.calendar() {
|
|||
max,
|
||||
rooms,
|
||||
schedules,
|
||||
allowEdit
|
||||
user != null
|
||||
)
|
||||
CalendarOrientation.TIME_TO_ROOM -> renderTimeToRoom(
|
||||
day,
|
||||
|
@ -335,7 +332,7 @@ fun Route.calendar() {
|
|||
max,
|
||||
rooms,
|
||||
schedules,
|
||||
allowEdit
|
||||
user != null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -345,15 +342,15 @@ fun Route.calendar() {
|
|||
}
|
||||
}
|
||||
|
||||
get<LocationCalendar.LocationCalendarEdit> { param ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.SCHEDULE)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val list = WorkGroup.list()
|
||||
val room = Room.get(param.room)
|
||||
val day = param.day
|
||||
val time = param.time
|
||||
get("/calendar/{day}/{room}/{time}") {
|
||||
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 search = call.parameters["search"] ?: ""
|
||||
|
||||
val list = WorkGroupRepository.all()
|
||||
val room = RoomRepository.get(roomId) ?: return@get
|
||||
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
|
@ -363,12 +360,12 @@ fun Route.calendar() {
|
|||
content {
|
||||
h1 { +"Select work groups" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = param.search
|
||||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/calendar/$day/${room.id}/$time/-1") {
|
||||
a("/calendar/$day") {
|
||||
button(classes = "form-btn btn-primary") {
|
||||
+"Delete"
|
||||
+"Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -395,7 +392,7 @@ fun Route.calendar() {
|
|||
}
|
||||
|
||||
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}"
|
||||
entry {
|
||||
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 user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.SCHEDULE)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
for (it in Schedule.getByRoom(param.room, param.day, param.time)) {
|
||||
PushService.notify(
|
||||
MessageDeleteCalendarEntry(
|
||||
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 room = RoomRepository.get(roomId) ?: return@get
|
||||
val workGroup = WorkGroupRepository.get(workGroupId) ?: return@get
|
||||
|
||||
val cellTime = (schedule.time / 15) * 15
|
||||
val schedule = Schedule(null, workGroup, room, day, time)
|
||||
ScheduleRepository.create(schedule)
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
val redirect = call.parameters["redirect"]
|
||||
call.respondRedirect(redirect ?: "/calendar/$day")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import de.kif.backend.LocationDashboard
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.view.MainTemplate
|
||||
import de.kif.backend.view.MenuTemplate
|
||||
import io.ktor.application.call
|
||||
import io.ktor.html.respondHtmlTemplate
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.sessions.get
|
||||
import io.ktor.sessions.sessions
|
||||
import kotlinx.html.h1
|
||||
|
||||
fun Route.dashboard() {
|
||||
get<LocationDashboard> {
|
||||
get("") {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.LocationLogin
|
||||
import de.kif.backend.LocationLogout
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.model.User
|
||||
import de.kif.backend.UserPrinciple
|
||||
import de.kif.backend.view.MainTemplate
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.auth.principal
|
||||
import io.ktor.html.respondHtmlTemplate
|
||||
import io.ktor.locations.location
|
||||
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 io.ktor.sessions.clear
|
||||
import io.ktor.sessions.get
|
||||
import io.ktor.sessions.sessions
|
||||
|
@ -21,12 +19,13 @@ import io.ktor.sessions.set
|
|||
import kotlinx.html.*
|
||||
|
||||
fun Route.login() {
|
||||
location<LocationLogin> {
|
||||
route("login") {
|
||||
authenticate {
|
||||
post {
|
||||
val principal = call.principal<User>() ?: return@post
|
||||
call.sessions.set(PortalSession(principal.id, principal.username))
|
||||
call.respondRedirect(call.parameters[LocationLogin::next.name] ?: "/")
|
||||
val principal = call.principal<UserPrinciple>() ?: return@post
|
||||
if (principal.user.id == null) return@post
|
||||
call.sessions.set(PortalSession(principal.user.id))
|
||||
call.respondRedirect(call.parameters["redirect"] ?: "/")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,43 +40,43 @@ fun Route.login() {
|
|||
form("/login", method = FormMethod.post) {
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = LocationLogin::username.name
|
||||
htmlFor = "username"
|
||||
+"Username"
|
||||
}
|
||||
input(
|
||||
name = LocationLogin::username.name,
|
||||
name = "username",
|
||||
classes = "form-control"
|
||||
) {
|
||||
id = LocationLogin::username.name
|
||||
id = "username"
|
||||
placeholder = "Username"
|
||||
}
|
||||
}
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = LocationLogin::password.name
|
||||
htmlFor = "password"
|
||||
+"Password"
|
||||
}
|
||||
input(
|
||||
name = LocationLogin::password.name,
|
||||
name = "password",
|
||||
classes = "form-control",
|
||||
type = InputType.password
|
||||
) {
|
||||
id = LocationLogin::password.name
|
||||
id = "password"
|
||||
placeholder = "Password"
|
||||
}
|
||||
}
|
||||
input(
|
||||
name = LocationLogin::next.name,
|
||||
name = "redirect",
|
||||
type = InputType.hidden
|
||||
) {
|
||||
value = call.parameters[LocationLogin::next.name] ?: "/"
|
||||
value = call.parameters["redirect"] ?: "/"
|
||||
}
|
||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
||||
+"Login"
|
||||
}
|
||||
}
|
||||
|
||||
if ("error" in call.parameters) {
|
||||
if ("onFailure" in call.parameters) {
|
||||
br { }
|
||||
div("alert alert-danger") {
|
||||
+"Username or password incorrect!"
|
||||
|
@ -88,15 +87,13 @@ fun Route.login() {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
call.respondRedirect(call.parameters[LocationLogin::next.name] ?: "/")
|
||||
call.respondRedirect(call.parameters["redirect"] ?: "/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
location<LocationLogout> {
|
||||
get {
|
||||
call.sessions.clear<PortalSession>()
|
||||
call.respondRedirect("/")
|
||||
}
|
||||
get("logout") {
|
||||
call.sessions.clear<PortalSession>()
|
||||
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
|
||||
|
||||
import de.kif.backend.LocationLogin
|
||||
import de.kif.backend.LocationRoom
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.model.Permission
|
||||
import de.kif.backend.model.Room
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.repository.RoomRepository
|
||||
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 de.kif.common.model.Permission
|
||||
import de.kif.common.model.Room
|
||||
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.routing.get
|
||||
import io.ktor.routing.post
|
||||
import io.ktor.util.toMap
|
||||
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() {
|
||||
get<LocationRoom> { param ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val list = Room.list()
|
||||
|
||||
get("/rooms") {
|
||||
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||
val search = call.parameters["search"] ?: ""
|
||||
val list = RoomRepository.all()
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -38,11 +38,11 @@ fun Route.room() {
|
|||
content {
|
||||
h1 { +"Rooms" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = param.search
|
||||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/room/new") {
|
||||
button(classes="form-btn btn-primary") {
|
||||
button(classes = "form-btn btn-primary") {
|
||||
+"Add room"
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ fun Route.room() {
|
|||
}
|
||||
|
||||
for (u in list) {
|
||||
if (Search.match(param.search, u.name)) {
|
||||
if (Search.match(search, u.name)) {
|
||||
entry {
|
||||
attributes["data-search"] = Search.pack(u.name)
|
||||
td {
|
||||
|
@ -90,12 +90,10 @@ fun Route.room() {
|
|||
}
|
||||
}
|
||||
|
||||
get<LocationRoom.Edit> { roomId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val editRoom = Room.get(roomId.id)
|
||||
get("/room/{id}") {
|
||||
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
val editRoom = RoomRepository.get(roomId) ?: return@get
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -175,31 +173,26 @@ fun Route.room() {
|
|||
}
|
||||
}
|
||||
|
||||
post<LocationRoom.Edit> { roomId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
post("/room/{id}") {
|
||||
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
val editRoom = Room.get(roomId.id)
|
||||
var room = RoomRepository.get(roomId) ?: return@post
|
||||
|
||||
params["name"]?.let { editRoom.name = it }
|
||||
params["places"]?.let { editRoom.places = it.toIntOrNull() ?: 0 }
|
||||
params["projector"]?.let { editRoom.projector = it == "on" }
|
||||
params["name"]?.let { room = room.copy(name = it) }
|
||||
params["places"]?.let { room = room.copy(places = it.toIntOrNull() ?: 0) }
|
||||
params["projector"]?.let { room = room.copy(projector = it == "on") }
|
||||
|
||||
editRoom.save()
|
||||
RoomRepository.update(room)
|
||||
|
||||
call.respondRedirect("/room")
|
||||
call.respondRedirect("/rooms")
|
||||
}
|
||||
}
|
||||
|
||||
get<LocationRoom.New> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
get("/room/new") {
|
||||
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -274,11 +267,8 @@ fun Route.room() {
|
|||
}
|
||||
}
|
||||
|
||||
post<LocationRoom.New> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
post("/room/new") {
|
||||
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
|
@ -287,22 +277,21 @@ fun Route.room() {
|
|||
val places = (params["places"] ?: return@post).toIntOrNull() ?: 0
|
||||
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 ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.ROOM)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val deleteRoom = Room.get(roomId.id)
|
||||
get("/room/{id}/delete") {
|
||||
authenticateOrRedirect(Permission.ROOM) { user ->
|
||||
val roomId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
|
||||
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
|
||||
|
||||
import de.kif.backend.LocationLogin
|
||||
import de.kif.backend.LocationTrack
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.model.Permission
|
||||
import de.kif.backend.model.Track
|
||||
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.repository.TrackRepository
|
||||
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 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.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.routing.get
|
||||
import io.ktor.routing.post
|
||||
import io.ktor.util.toMap
|
||||
import kif.common.model.Color
|
||||
import kotlinx.css.CSSBuilder
|
||||
import kotlinx.html.*
|
||||
import kotlin.collections.set
|
||||
import kotlin.random.Random
|
||||
|
||||
fun DIV.colorPicker(color: Color?) {
|
||||
|
@ -85,12 +83,10 @@ fun DIV.colorPicker(color: Color?) {
|
|||
}
|
||||
|
||||
fun Route.track() {
|
||||
get<LocationTrack> { param ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val list = Track.list()
|
||||
get("/tracks") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val search = call.parameters["search"] ?: ""
|
||||
val list = TrackRepository.all()
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -99,7 +95,7 @@ fun Route.track() {
|
|||
content {
|
||||
h1 { +"Tracks" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = param.search
|
||||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/track/new") {
|
||||
|
@ -122,7 +118,7 @@ fun Route.track() {
|
|||
}
|
||||
|
||||
for (u in list) {
|
||||
if (Search.match(param.search, u.name)) {
|
||||
if (Search.match(search, u.name)) {
|
||||
entry {
|
||||
attributes["data-search"] = Search.pack(u.name)
|
||||
td {
|
||||
|
@ -145,12 +141,10 @@ fun Route.track() {
|
|||
}
|
||||
}
|
||||
|
||||
get<LocationTrack.Edit> { trackId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val editTrack = Track.get(trackId.id)
|
||||
get("/track/{id}") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
val editTrack = TrackRepository.get(trackId) ?: return@get
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -198,33 +192,28 @@ fun Route.track() {
|
|||
}
|
||||
}
|
||||
|
||||
post<LocationTrack.Edit> { trackId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
post("/track/{id}") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
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 }
|
||||
}?.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> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
get("/track/new") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -266,11 +255,8 @@ fun Route.track() {
|
|||
}
|
||||
}
|
||||
|
||||
post<LocationTrack.New> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
post("/track/new") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
|
@ -280,25 +266,21 @@ fun Route.track() {
|
|||
Color.default.find { it.first == c }
|
||||
}?.second ?: (params["color-input"] ?: return@post).let(Color.Companion::parse)
|
||||
|
||||
Track(
|
||||
name = name,
|
||||
color = color
|
||||
).save()
|
||||
val track = Track(null, name, color)
|
||||
|
||||
call.respondRedirect("/track")
|
||||
TrackRepository.create(track)
|
||||
|
||||
call.respondRedirect("/tracks")
|
||||
}
|
||||
}
|
||||
|
||||
get<LocationTrack.Delete> { trackId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val deleteTrack = Track.get(trackId.id)
|
||||
get("track/{id}/delete") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val trackId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
|
||||
deleteTrack.delete()
|
||||
TrackRepository.delete(trackId)
|
||||
|
||||
call.respondRedirect("/track")
|
||||
call.respondRedirect("/tracks")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,40 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.LocationLogin
|
||||
import de.kif.backend.LocationUser
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.model.Permission
|
||||
import de.kif.backend.model.User
|
||||
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.hashPassword
|
||||
import de.kif.backend.repository.UserRepository
|
||||
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 de.kif.common.model.Permission
|
||||
import de.kif.common.model.User
|
||||
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.routing.get
|
||||
import io.ktor.routing.post
|
||||
import io.ktor.util.toMap
|
||||
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() {
|
||||
get<LocationUser> { param ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val list = User.list()
|
||||
get("/users") { param ->
|
||||
authenticateOrRedirect(Permission.USER) { user ->
|
||||
val search = call.parameters["search"] ?: ""
|
||||
val list = UserRepository.all()
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -38,11 +43,11 @@ fun Route.user() {
|
|||
content {
|
||||
h1 { +"Users" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = param.search
|
||||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/user/new") {
|
||||
button(classes="form-btn btn-primary") {
|
||||
button(classes = "form-btn btn-primary") {
|
||||
+"Add user"
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +66,7 @@ fun Route.user() {
|
|||
}
|
||||
|
||||
for (u in list) {
|
||||
if (Search.match(param.search, u.username)) {
|
||||
if (Search.match(search, u.username)) {
|
||||
entry {
|
||||
attributes["data-search"] = Search.pack(u.username)
|
||||
td {
|
||||
|
@ -84,12 +89,10 @@ fun Route.user() {
|
|||
}
|
||||
}
|
||||
|
||||
get<LocationUser.Edit> { userId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val editUser = User.get(userId.id) ?: return@get
|
||||
get("/user/{id}") {
|
||||
authenticateOrRedirect(Permission.USER) { user ->
|
||||
val userId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
val editUser = UserRepository.get(userId) ?: return@get
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -158,39 +161,31 @@ fun Route.user() {
|
|||
}
|
||||
}
|
||||
|
||||
post<LocationUser.Edit> { userId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
post("/user/{id}") {
|
||||
authenticateOrRedirect(Permission.USER) { user ->
|
||||
val userId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
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()
|
||||
if (user.checkPermission(permission)) {
|
||||
if (params["permission-$name"] == "on") {
|
||||
editUser.permissions += permission
|
||||
} else {
|
||||
editUser.permissions -= permission
|
||||
}
|
||||
}
|
||||
}
|
||||
editUser.save()
|
||||
user.checkPermission(permission) && params["permission-$name"] == "on"
|
||||
}.toSet()
|
||||
editUser = editUser.copy(permissions = permissions)
|
||||
|
||||
call.respondRedirect("/user")
|
||||
UserRepository.update(user)
|
||||
|
||||
call.respondRedirect("/users")
|
||||
}
|
||||
}
|
||||
|
||||
get<LocationUser.New> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
get("/user/new") {
|
||||
authenticateOrRedirect(Permission.USER) { user ->
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -269,11 +264,8 @@ fun Route.user() {
|
|||
}
|
||||
}
|
||||
|
||||
post<LocationUser.New> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
post("/user/new") {
|
||||
authenticateOrRedirect(Permission.USER) { user ->
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
|
@ -288,26 +280,26 @@ fun Route.user() {
|
|||
null
|
||||
}.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 ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.USER)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val deleteUser = User.get(userId.id) ?: return@get
|
||||
get("/user/{id}/delete") {
|
||||
authenticateOrRedirect(Permission.USER) { user ->
|
||||
val userId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
val deleteUser = UserRepository.get(userId) ?: return@get
|
||||
|
||||
if (user.checkPermission(Permission.USER) &&
|
||||
(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
|
||||
|
||||
import de.kif.backend.LocationLogin
|
||||
import de.kif.backend.LocationWorkGroup
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.database.Language
|
||||
import de.kif.backend.model.Permission
|
||||
import de.kif.backend.model.Track
|
||||
import de.kif.backend.model.WorkGroup
|
||||
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.repository.TrackRepository
|
||||
import de.kif.backend.repository.WorkGroupRepository
|
||||
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 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.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.routing.get
|
||||
import io.ktor.routing.post
|
||||
import io.ktor.util.toMap
|
||||
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() {
|
||||
get<LocationWorkGroup> { param ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val list = WorkGroup.list()
|
||||
get("workgroups") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val search = call.parameters["search"] ?: ""
|
||||
val list = WorkGroupRepository.all()
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -40,10 +41,10 @@ fun Route.workGroup() {
|
|||
content {
|
||||
h1 { +"Work groups" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = param.search
|
||||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/track") {
|
||||
a("/tracks") {
|
||||
button(classes = "form-btn") {
|
||||
+"Edit tracks"
|
||||
}
|
||||
|
@ -83,7 +84,7 @@ fun Route.workGroup() {
|
|||
}
|
||||
|
||||
for (u in list) {
|
||||
if (Search.match(param.search, u.name)) {
|
||||
if (Search.match(search, u.name)) {
|
||||
entry {
|
||||
attributes["data-search"] = Search.pack(u.name)
|
||||
td {
|
||||
|
@ -121,13 +122,11 @@ fun Route.workGroup() {
|
|||
}
|
||||
}
|
||||
|
||||
get<LocationWorkGroup.Edit> { workGroupId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val editWorkGroup = WorkGroup.get(workGroupId.id)
|
||||
val tracks = Track.list()
|
||||
get("/workgroup/{id}") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val workGroupId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
val editWorkGroup = WorkGroupRepository.get(workGroupId) ?: return@get
|
||||
val tracks = TrackRepository.all()
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -179,20 +178,20 @@ fun Route.workGroup() {
|
|||
name = "track"
|
||||
|
||||
option {
|
||||
selected = (editWorkGroup.trackId ?: -1) < 0
|
||||
selected = (editWorkGroup.track?.id ?: -1) < 0
|
||||
value = "-1"
|
||||
+"None"
|
||||
}
|
||||
for (track in tracks) {
|
||||
option {
|
||||
selected = editWorkGroup.trackId == track.id
|
||||
selected = editWorkGroup.track?.id == track.id
|
||||
value = track.id.toString()
|
||||
+track.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a("/track", classes = "form-btn") {
|
||||
a("/tracks", classes = "form-btn") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
|
@ -228,8 +227,8 @@ fun Route.workGroup() {
|
|||
for (language in Language.values()) {
|
||||
option {
|
||||
selected = editWorkGroup.language == language
|
||||
value = language.name
|
||||
+language.toString()
|
||||
value = language.code
|
||||
+language.localeName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -287,36 +286,37 @@ fun Route.workGroup() {
|
|||
}
|
||||
}
|
||||
|
||||
post<LocationWorkGroup.Edit> { workGroupId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
post("/workgroup/{id}") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val workGroupId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
val editWorkGroup = WorkGroup.get(workGroupId.id)
|
||||
var editWorkGroup = WorkGroupRepository.get(workGroupId) ?: return@post
|
||||
|
||||
params["name"]?.let { editWorkGroup.name = it }
|
||||
params["interested"]?.toIntOrNull()?.let { editWorkGroup.interested = it }
|
||||
params["track"]?.toIntOrNull()?.let { editWorkGroup.trackId = it }
|
||||
params["projector"]?.let { editWorkGroup.projector = it == "on" }
|
||||
params["resolution"]?.let { editWorkGroup.resolution = it == "on" }
|
||||
params["length"]?.toIntOrNull()?.let { editWorkGroup.length = it }
|
||||
params["language"]?.let { editWorkGroup.language = Language.values().find { l -> l.name == it } ?: Language.GERMAN }
|
||||
params["name"]?.let { editWorkGroup = editWorkGroup.copy(name = it) }
|
||||
params["interested"]?.toIntOrNull()?.let { editWorkGroup = editWorkGroup.copy(interested = it) }
|
||||
params["track"]?.toLongOrNull()?.let {
|
||||
val track = TrackRepository.get(it)
|
||||
editWorkGroup = editWorkGroup.copy(track = track)
|
||||
}
|
||||
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> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val tracks = Track.list()
|
||||
get("/workgroup/new") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val tracks = TrackRepository.all()
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -410,8 +410,8 @@ fun Route.workGroup() {
|
|||
for (language in Language.values()) {
|
||||
option {
|
||||
selected = language == Language.GERMAN
|
||||
value = language.name
|
||||
+language.toString()
|
||||
value = language.code
|
||||
+language.localeName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -464,47 +464,46 @@ fun Route.workGroup() {
|
|||
}
|
||||
}
|
||||
|
||||
post<LocationWorkGroup.New> {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
post("/workgroup/new") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
|
||||
val name = params["name"] ?: return@post
|
||||
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 resolution = params["resolution"] == "on"
|
||||
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,
|
||||
interested = interested,
|
||||
trackId = trackId,
|
||||
track = track,
|
||||
projector = projector,
|
||||
resolution = resolution,
|
||||
length = length,
|
||||
language = language
|
||||
).save()
|
||||
)
|
||||
|
||||
call.respondRedirect("/workgroup")
|
||||
WorkGroupRepository.create(workGroup)
|
||||
|
||||
call.respondRedirect("/workgroups")
|
||||
}
|
||||
}
|
||||
|
||||
get<LocationWorkGroup.Delete> { workGroupId ->
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
if (user == null || !user.checkPermission(Permission.WORK_GROUP)) {
|
||||
call.respondRedirect("/login?${LocationLogin::next.name}=${call.request.path()}}")
|
||||
} else {
|
||||
val deleteWorkGroup = WorkGroup.get(workGroupId.id)
|
||||
get("/workgroup/{id}/delete") {
|
||||
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
|
||||
val workGroupId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
|
||||
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.readText
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.websocket.WebSocketServerSession
|
||||
import io.ktor.websocket.webSocket
|
||||
import kif.common.model.Message
|
||||
import kif.common.model.MessageType
|
||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||
import java.lang.Exception
|
||||
|
||||
object PushService {
|
||||
|
||||
var clients: List<WebSocketServerSession> = emptyList()
|
||||
|
||||
suspend fun notify(messageType: MessageType) {
|
||||
suspend fun notify(type: MessageType, repository: RepositoryType, id: Long) {
|
||||
try {
|
||||
val data = Message(messageType).stringify()
|
||||
val data = Message(type, repository, id).stringify()
|
||||
for (client in clients) {
|
||||
client.outgoing.send(Frame.Text(data))
|
||||
}
|
||||
|
@ -39,9 +39,15 @@ fun Route.pushService() {
|
|||
} catch (_: ClosedReceiveChannelException) {
|
||||
PushService.clients -= this
|
||||
} catch (e: Throwable) {
|
||||
println("onError ${closeReason.await()}")
|
||||
println("onFailure ${closeReason.await()}")
|
||||
e.printStackTrace()
|
||||
PushService.clients -= this
|
||||
}
|
||||
}
|
||||
|
||||
RoomRepository.registerPushService()
|
||||
ScheduleRepository.registerPushService()
|
||||
TrackRepository.registerPushService()
|
||||
UserRepository.registerPushService()
|
||||
WorkGroupRepository.registerPushService()
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
package de.kif.backend.view
|
||||
|
||||
import de.kif.backend.model.Permission
|
||||
import de.kif.backend.model.User
|
||||
import de.kif.common.model.Permission
|
||||
import de.kif.common.model.User
|
||||
import io.ktor.html.Template
|
||||
import kotlinx.html.*
|
||||
|
||||
class MenuTemplate() : Template<FlowContent> {
|
||||
|
||||
var setup = false
|
||||
var active: Tab = Tab.DASHBOARD
|
||||
var user: User? = null
|
||||
|
||||
|
@ -15,55 +14,42 @@ class MenuTemplate() : Template<FlowContent> {
|
|||
nav("menu") {
|
||||
div("container") {
|
||||
div("menu-left") {
|
||||
if (setup) {
|
||||
a(classes = "active") {
|
||||
+"Setup"
|
||||
}
|
||||
} else {
|
||||
a("/", classes = if (active == Tab.DASHBOARD) "active" else null) {
|
||||
+"Dashboard"
|
||||
}
|
||||
a("/calendar", classes = if (active == Tab.CALENDAR) "active" else null) {
|
||||
+"Calendar"
|
||||
}
|
||||
a("/", classes = if (active == Tab.DASHBOARD) "active" else null) {
|
||||
+"Dashboard"
|
||||
}
|
||||
a("/calendar", classes = if (active == Tab.CALENDAR) "active" else null) {
|
||||
+"Calendar"
|
||||
}
|
||||
}
|
||||
if (!setup) {
|
||||
div("menu-right") {
|
||||
span("menu-icon") {
|
||||
i("material-icons") { +"menu" }
|
||||
}
|
||||
val user = user
|
||||
div("menu-content") {
|
||||
if (user == null) {
|
||||
a("/account", classes = if (active == Tab.ACCOUNT) "active" else null) {
|
||||
+"Login"
|
||||
div("menu-right") {
|
||||
span("menu-icon") {
|
||||
i("material-icons") { +"menu" }
|
||||
}
|
||||
val user = user
|
||||
div("menu-content") {
|
||||
if (user == null) {
|
||||
a("/account", classes = if (active == Tab.ACCOUNT) "active" else null) {
|
||||
+"Login"
|
||||
}
|
||||
} else {
|
||||
if (user.checkPermission(Permission.WORK_GROUP)) {
|
||||
a("/workgroups", classes = if (active == Tab.WORK_GROUP) "active" else null) {
|
||||
+"Work groups"
|
||||
}
|
||||
} else {
|
||||
if (user.checkPermission(Permission.WORK_GROUP)) {
|
||||
a("/workgroup", classes = if (active == Tab.WORK_GROUP) "active" else null) {
|
||||
+"Work groups"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.ROOM)) {
|
||||
a("/rooms", classes = if (active == Tab.ROOM) "active" else null) {
|
||||
+"Rooms"
|
||||
}
|
||||
if (user.checkPermission(Permission.ROOM)) {
|
||||
a("/room", classes = if (active == Tab.ROOM) "active" else null) {
|
||||
+"Rooms"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.PERSON)) {
|
||||
a("/person", classes = if (active == Tab.PERSON) "active" else null) {
|
||||
+"Persons"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.PERSON)) {
|
||||
a("/user", classes = if (active == Tab.USER) "active" else null) {
|
||||
+"Users"
|
||||
}
|
||||
}
|
||||
a("/account", classes = if (active == Tab.ACCOUNT) "active" else null) {
|
||||
+user.username
|
||||
}
|
||||
if (user.checkPermission(Permission.PERSON)) {
|
||||
a("/users", classes = if (active == Tab.USER) "active" else null) {
|
||||
+"Users"
|
||||
}
|
||||
}
|
||||
a("/account", classes = if (active == Tab.ACCOUNT) "active" else null) {
|
||||
+user.username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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