This commit is contained in:
@@ -2,8 +2,10 @@ package de.walamana
|
||||
|
||||
import de.walamana.plugins.*
|
||||
import de.walamana.views.*
|
||||
import io.github.cdimascio.dotenv.dotenv
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.http.content.*
|
||||
import io.ktor.server.netty.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
@@ -12,7 +14,14 @@ import io.ktor.server.routing.*
|
||||
// .start(wait = true)
|
||||
//}
|
||||
|
||||
fun main(args: Array<String>): Unit = EngineMain.main(args)
|
||||
fun main(args: Array<String>): Unit {
|
||||
dotenv {
|
||||
ignoreIfMissing = true
|
||||
ignoreIfMalformed = true
|
||||
systemProperties = true
|
||||
}
|
||||
EngineMain.main(args)
|
||||
}
|
||||
|
||||
fun Application.module() {
|
||||
configureSecurity()
|
||||
@@ -21,6 +30,9 @@ fun Application.module() {
|
||||
configureDatabases()
|
||||
|
||||
routing {
|
||||
singlePageApplication {
|
||||
vue(System.getenv("FRONTEND_PATH"))
|
||||
}
|
||||
route("/api") {
|
||||
configureMinistrantenRoutes()
|
||||
configureGottesdiensteRoutes()
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package de.walamana.models
|
||||
|
||||
import de.walamana.plugins.DateAsLong
|
||||
import de.walamana.plugins.dbQuery
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import java.util.Date
|
||||
|
||||
@Serializable
|
||||
data class GottesdienstGroup (
|
||||
val id: Int,
|
||||
val from: DateAsLong,
|
||||
val to: DateAsLong
|
||||
)
|
||||
|
||||
|
||||
object GottesdienstGroups : Table() {
|
||||
val id = integer("id").autoIncrement()
|
||||
val from = long("date_from")
|
||||
val to = long("date_to")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
object GottesdienstGroupDao {
|
||||
|
||||
fun ResultRow.toPlan() = GottesdienstGroup(
|
||||
this[GottesdienstGroups.id],
|
||||
Date(this[GottesdienstGroups.from]),
|
||||
Date(this[GottesdienstGroups.to])
|
||||
)
|
||||
|
||||
suspend fun getPlans(amount: Int = 10, offset: Long = 0) = dbQuery {
|
||||
GottesdienstGroups.selectAll()
|
||||
.orderBy(GottesdienstGroups.from to SortOrder.DESC)
|
||||
.limit(amount, offset)
|
||||
.map { it.toPlan() }
|
||||
}
|
||||
|
||||
suspend fun createPlan(from: Date, to: Date) = dbQuery {
|
||||
GottesdienstGroups.insert {
|
||||
it[GottesdienstGroups.from] = from.time
|
||||
it[GottesdienstGroups.to] = to.time
|
||||
}.resultedValues?.firstOrNull()?.toPlan()
|
||||
}
|
||||
|
||||
suspend fun updatePlan(group: GottesdienstGroup): Boolean = dbQuery {
|
||||
GottesdienstGroups.upsert {
|
||||
it[id]= group.id
|
||||
it[from] = group.from.time
|
||||
it[to] = group.to.time
|
||||
}.resultedValues?.isNotEmpty() ?: false
|
||||
}
|
||||
|
||||
suspend fun deletePlan(id: Int) = dbQuery {
|
||||
GottesdienstGroups.deleteWhere { GottesdienstGroups.id eq id } > 0
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,9 @@ object Gottesdienste : Table() {
|
||||
val date = long("date")
|
||||
val attendance = long("attendance")
|
||||
val planId = integer("planId")
|
||||
.references(GottesdienstGroups.id, onDelete = ReferenceOption.CASCADE)
|
||||
|
||||
override val primaryKey = PrimaryKey(id, planId)
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
object GottesdiensteDao {
|
||||
@@ -72,4 +73,4 @@ object GottesdiensteDao {
|
||||
if (planId != null) it[Gottesdienste.planId] = planId
|
||||
} > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import de.walamana.plugins.dbQuery
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
@Serializable
|
||||
data class Mark(
|
||||
@@ -14,9 +15,9 @@ data class Mark(
|
||||
|
||||
object Marks : Table() {
|
||||
val mid = integer("mid")
|
||||
.references(Ministranten.id);
|
||||
.references(Ministranten.id, onDelete = ReferenceOption.CASCADE);
|
||||
val gid = integer("gid")
|
||||
.references(Gottesdienste.id);
|
||||
.references(Gottesdienste.id, onDelete = ReferenceOption.CASCADE);
|
||||
val value = integer("value");
|
||||
|
||||
override val primaryKey = PrimaryKey(mid, gid)
|
||||
@@ -51,4 +52,4 @@ object MarksDao {
|
||||
} > 0
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.*
|
||||
data class Ministrant(
|
||||
val id: Int,
|
||||
val username: String,
|
||||
val passwordHash: String,
|
||||
val passwordHash: String? = "",
|
||||
val firstname: String,
|
||||
val lastname: String,
|
||||
val birthday: DateAsLong,
|
||||
@@ -56,7 +56,12 @@ object MinistrantenDao {
|
||||
)
|
||||
|
||||
suspend fun allMinistranten(): List<Ministrant> = dbQuery {
|
||||
Ministranten.selectAll().map(::resultRowToMinistrant)
|
||||
Ministranten.selectAll()
|
||||
.orderBy(
|
||||
Ministranten.lastname to SortOrder.ASC,
|
||||
Ministranten.firstname to SortOrder.ASC
|
||||
)
|
||||
.map(::resultRowToMinistrant)
|
||||
}
|
||||
|
||||
suspend fun simplifiedMinistranten(): List<SimplifiedMinistrant> = dbQuery {
|
||||
@@ -65,14 +70,19 @@ object MinistrantenDao {
|
||||
Ministranten.username,
|
||||
Ministranten.firstname,
|
||||
Ministranten.lastname
|
||||
).selectAll().map { row ->
|
||||
SimplifiedMinistrant(
|
||||
row[Ministranten.id],
|
||||
row[Ministranten.username],
|
||||
row[Ministranten.firstname],
|
||||
row[Ministranten.lastname]
|
||||
).selectAll()
|
||||
.orderBy(
|
||||
Ministranten.lastname to SortOrder.ASC,
|
||||
Ministranten.firstname to SortOrder.ASC
|
||||
)
|
||||
}
|
||||
.map { row ->
|
||||
SimplifiedMinistrant(
|
||||
row[Ministranten.id],
|
||||
row[Ministranten.username],
|
||||
row[Ministranten.firstname],
|
||||
row[Ministranten.lastname]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getMinistrant(username: String, showPasswordHash: Boolean = false): Ministrant? = dbQuery {
|
||||
@@ -123,4 +133,4 @@ object MinistrantenDao {
|
||||
if (privileges != null) it[Ministranten.privileges] = privileges.joinToString(",")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.walamana.plugins
|
||||
|
||||
import de.walamana.models.GottesdienstGroups
|
||||
import de.walamana.models.Gottesdienste
|
||||
import de.walamana.models.Marks
|
||||
import de.walamana.models.Ministranten
|
||||
@@ -11,16 +12,25 @@ import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransacti
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
fun Application.configureDatabases() {
|
||||
val driverClassName = "org.h2.Driver"
|
||||
val jdbcURL = "jdbc:h2:file:./build/db"
|
||||
val database = Database.connect(jdbcURL, driverClassName)
|
||||
val driverClassName = environment.config.property("database.driver").getString()
|
||||
val jdbcURL = environment.config.property("database.url").getString()
|
||||
val user = environment.config.propertyOrNull("database.user")?.getString()
|
||||
val password = environment.config.propertyOrNull("database.password")?.getString()
|
||||
|
||||
println("Using database driver $driverClassName")
|
||||
|
||||
val database = if(user != null && password != null )
|
||||
Database.connect(jdbcURL, driverClassName, user, password)
|
||||
else
|
||||
Database.connect(jdbcURL, driverClassName)
|
||||
|
||||
transaction {
|
||||
SchemaUtils.create(Gottesdienste)
|
||||
SchemaUtils.create(Ministranten)
|
||||
SchemaUtils.create(GottesdienstGroups)
|
||||
SchemaUtils.create(Gottesdienste)
|
||||
SchemaUtils.create(Marks)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> dbQuery(block: suspend () -> T): T =
|
||||
newSuspendedTransaction(Dispatchers.IO) { block() }
|
||||
suspend fun <T> dbQuery(block: suspend Transaction.() -> T): T =
|
||||
newSuspendedTransaction(Dispatchers.IO) { block() }
|
||||
|
||||
@@ -62,10 +62,15 @@ object Security {
|
||||
fun DEFAULT_EXPIRY() = Date(System.currentTimeMillis() + 1000 * 60 * 60);
|
||||
|
||||
suspend fun authenticateUser(application: Application, username: String, password: String): Ministrant? {
|
||||
println("Username $username password $password")
|
||||
|
||||
if (username == "admin") {
|
||||
println("Test")
|
||||
val adminPw = application.environment.config.property("admin.password").getString()
|
||||
println("Test $adminPw")
|
||||
if (adminPw == password) {
|
||||
val allMinis = MinistrantenDao.allMinistranten().map { it.username }
|
||||
println(allMinis)
|
||||
return Ministrant(
|
||||
0, "admin", "", "admin", "admin", Date(), allMinis
|
||||
)
|
||||
@@ -103,4 +108,4 @@ object Security {
|
||||
.withClaim("privileges", ministrant.privileges)
|
||||
.withExpiresAt(DEFAULT_EXPIRY())
|
||||
.sign(Algorithm.HMAC256(jwtEnv.secret))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ fun Application.configureSerialization() {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -13,12 +13,17 @@ data class Plan (
|
||||
|
||||
object PlanDao {
|
||||
|
||||
suspend fun constructPlan(planId: Int): Plan {
|
||||
suspend fun constructPlan(planId: Int, isAnonymous: Boolean): Plan {
|
||||
val gottesdienste = GottesdiensteDao.getGottesdiensteForPlan(planId)
|
||||
val ministranten = MinistrantenDao.simplifiedMinistranten()
|
||||
var ministranten = MinistrantenDao.simplifiedMinistranten()
|
||||
if(isAnonymous) {
|
||||
ministranten = ministranten.map {
|
||||
SimplifiedMinistrant(it.id, it.username, it.firstname, if(it.lastname.isNotEmpty()) it.lastname[0].toString() + "." else "")
|
||||
}
|
||||
}
|
||||
val marks = MarksDao.allMarksForPlan(planId)
|
||||
|
||||
return Plan(gottesdienste, ministranten, marks)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,12 @@ fun Route.configureAuthenticationRoutes() {
|
||||
return@post
|
||||
}
|
||||
val request = call.receive<PasswordResetRequest>()
|
||||
val newPassword = BCrypt.withDefaults()
|
||||
.hash(Math.random().toInt() * 3 + 4, Math.random().toString().toCharArray())
|
||||
.toString().substring(3..10);
|
||||
var newPassword = "";
|
||||
for(i in 0..2) {
|
||||
newPassword += BCrypt.withDefaults()
|
||||
.hash(Math.random().toInt() * 3 + 4, (Math.random().toString() + Math.random().toString()).toCharArray())
|
||||
};
|
||||
newPassword = newPassword.substring(3..10)
|
||||
|
||||
Security.setPassword(request.username, newPassword)
|
||||
|
||||
@@ -94,4 +97,4 @@ fun Route.configureAuthenticationRoutes() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.walamana.views
|
||||
|
||||
import de.walamana.models.Ministrant
|
||||
import de.walamana.models.MinistrantenDao
|
||||
import de.walamana.models.SimplifiedMinistrant
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.request.*
|
||||
@@ -17,9 +18,10 @@ fun Route.configureMinistrantenRoutes() {
|
||||
}
|
||||
put {
|
||||
val data = call.receive<Ministrant>()
|
||||
val passwordHash = data.passwordHash ?: return@put
|
||||
val ministrant = MinistrantenDao.createMinistrant(
|
||||
data.username,
|
||||
data.passwordHash,
|
||||
passwordHash,
|
||||
data.firstname,
|
||||
data.lastname,
|
||||
data.birthday,
|
||||
@@ -29,16 +31,27 @@ fun Route.configureMinistrantenRoutes() {
|
||||
}
|
||||
patch {
|
||||
// TODO: Access only by admin
|
||||
val data = call.receive<Ministrant>()
|
||||
val changed = MinistrantenDao.updateMinistrant(
|
||||
data.id,
|
||||
data.username,
|
||||
data.passwordHash,
|
||||
data.firstname,
|
||||
data.lastname,
|
||||
data.birthday,
|
||||
data.privileges
|
||||
)
|
||||
val simple = false
|
||||
if(simple) {
|
||||
val data = call.receive<SimplifiedMinistrant>()
|
||||
val changed = MinistrantenDao.updateMinistrant(
|
||||
id = data.id,
|
||||
username = data.username,
|
||||
firstname = data.firstname,
|
||||
lastname = data.lastname
|
||||
)
|
||||
}else{
|
||||
val data = call.receive<Ministrant>()
|
||||
val changed = MinistrantenDao.updateMinistrant(
|
||||
data.id,
|
||||
data.username,
|
||||
null,
|
||||
data.firstname,
|
||||
data.lastname,
|
||||
data.birthday,
|
||||
data.privileges
|
||||
)
|
||||
}
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
delete {
|
||||
@@ -47,4 +60,4 @@ fun Route.configureMinistrantenRoutes() {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package de.walamana.views
|
||||
|
||||
import de.walamana.models.GottesdienstGroup
|
||||
import de.walamana.models.GottesdienstGroupDao
|
||||
import de.walamana.models.GottesdienstGroups
|
||||
import de.walamana.service.PlanDao
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
import io.ktor.server.auth.jwt.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
@@ -10,9 +15,34 @@ import io.ktor.server.util.*
|
||||
fun Route.configurePlanRoutes() {
|
||||
route("/plan") {
|
||||
get {
|
||||
val principal = call.principal<JWTPrincipal>()
|
||||
val isAnonymous = principal == null
|
||||
val id = call.parameters.getOrFail("id").toInt()
|
||||
val plan = PlanDao.constructPlan(id);
|
||||
val plan = PlanDao.constructPlan(id, isAnonymous = isAnonymous);
|
||||
call.respond(plan)
|
||||
}
|
||||
}
|
||||
}
|
||||
route("/groups") {
|
||||
get {
|
||||
val offset = call.parameters.get("offset")?.toLong() ?: 0
|
||||
val amount = call.parameters.get("amount")?.toInt() ?: 10
|
||||
val groups = GottesdienstGroupDao.getPlans(amount, offset)
|
||||
call.respond(groups)
|
||||
}
|
||||
post {
|
||||
val group = call.receive<GottesdienstGroup>()
|
||||
val res = GottesdienstGroupDao.createPlan(group.from, group.to) ?: TODO("fail")
|
||||
call.respond(res)
|
||||
}
|
||||
patch {
|
||||
val group = call.receive<GottesdienstGroup>()
|
||||
val res = GottesdienstGroupDao.updatePlan(group) ?: TODO("fail")
|
||||
call.respond(res)
|
||||
}
|
||||
delete {
|
||||
val id = call.parameters.getOrFail("id").toInt()
|
||||
val res = GottesdienstGroupDao.deletePlan(id)
|
||||
call.respond(hashMapOf("success" to res))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
ktor {
|
||||
development = true
|
||||
deployment {
|
||||
port = 8080
|
||||
}
|
||||
application {
|
||||
modules = [ de.walamana.ApplicationKt.module ]
|
||||
}
|
||||
}
|
||||
|
||||
jwt {
|
||||
secret = ${SECRET}
|
||||
issuer = "http://0.0.0.0:8080/"
|
||||
audience = "http://0.0.0.0:8080/"
|
||||
realm = "mini-data"
|
||||
}
|
||||
|
||||
admin {
|
||||
password = ${ADMIN}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import io.ktor.server.testing.*
|
||||
import kotlin.test.*
|
||||
|
||||
class ApplicationTest {
|
||||
@Test
|
||||
fun testRoot() = testApplication {
|
||||
// @Test
|
||||
// fun testRoot() = testApplication {
|
||||
// application {
|
||||
// configureRouting()
|
||||
// }
|
||||
@@ -17,5 +17,5 @@ class ApplicationTest {
|
||||
// assertEquals(HttpStatusCode.OK, status)
|
||||
// assertEquals("Hello World!", bodyAsText())
|
||||
// }
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user