Cleaner code

This commit is contained in:
Hubert Van De Walle 2020-07-01 00:12:56 +02:00
parent 0993d93ccc
commit e03e12110b
31 changed files with 223 additions and 223 deletions

View File

@ -16,7 +16,6 @@ jwt:
validity: 1 validity: 1
unit: HOURS unit: HOURS
refresh: refresh:
secret: ${JWT_REFRESH_SECRET=-wWchkx44YGig4Q5Z7b7+E/3ymGEGd6PS7UGedMul3bg=} # Can be generated with `openssl rand -base64 32` secret: ${JWT_REFRESH_SECRET=-wWchkx44YGig4Q5Z7b7+E/3ymGEGd6PS7UGedMul3bg=} # Can be generated with `openssl rand -base64 32`
validity: 15 validity: 15
unit: DAYS unit: DAYS

21
api/src/Configuration.kt Normal file
View File

@ -0,0 +1,21 @@
package be.vandewalleh
import com.sksamuel.hoplite.Masked
import java.util.concurrent.TimeUnit
data class Config(val database: DatabaseConfig, val server: ServerConfig, val jwt: JwtConfig) {
override fun toString(): String {
return """
Config(
database=$database,
server=$server,
jwt=$jwt
)
""".trimIndent()
}
}
data class DatabaseConfig(val host: String, val port: Int, val name: String, val username: String, val password: Masked)
data class ServerConfig(val host: String, val port: Int, val cors: Boolean)
data class JwtConfig(val auth: Jwt, val refresh: Jwt)
data class Jwt(val validity: Long, val unit: TimeUnit, val secret: Masked)

View File

@ -1,24 +1,40 @@
package be.vandewalleh package be.vandewalleh
import be.vandewalleh.features.BcryptPasswordHash import be.vandewalleh.auth.AuthenticationModule
import be.vandewalleh.features.PasswordHash import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.features.configurationModule import be.vandewalleh.extensions.ApplicationBuilder
import be.vandewalleh.migrations.Migration import be.vandewalleh.factories.configurationFactory
import be.vandewalleh.services.serviceModule import be.vandewalleh.factories.dataSourceFactory
import me.liuwj.ktorm.database.* import be.vandewalleh.factories.databaseFactory
import be.vandewalleh.factories.simpleJwtFactory
import be.vandewalleh.features.*
import be.vandewalleh.services.NoteService
import be.vandewalleh.services.UserService
import org.kodein.di.Kodein import org.kodein.di.Kodein
import org.kodein.di.generic.bind import org.kodein.di.generic.*
import org.kodein.di.generic.instance
import org.kodein.di.generic.singleton
import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import javax.sql.DataSource
val mainModule = Kodein.Module("main") { val mainModule = Kodein.Module("main") {
import(serviceModule) bind() from singleton { NoteService(instance()) }
import(configurationModule) bind() from singleton { UserService(instance(), instance()) }
bind<Logger>() with singleton { LoggerFactory.getLogger("Application") }
bind<Migration>() with singleton { Migration(this.kodein) } bind() from singleton { configurationFactory() }
bind<Database>() with singleton { Database.connect(this.instance<DataSource>()) }
bind() from setBinding<ApplicationBuilder>()
bind<ApplicationBuilder>().inSet() with singleton { ErrorHandler() }
bind<ApplicationBuilder>().inSet() with singleton { ContentNegotiationFeature() }
bind<ApplicationBuilder>().inSet() with singleton { CorsFeature(instance<Config>().server.cors) }
bind<ApplicationBuilder>().inSet() with singleton { AuthenticationModule(instance(tag = "auth")) }
bind<ApplicationBuilder>().inSet() with singleton { MigrationHook(instance()) }
bind<ApplicationBuilder>().inSet() with singleton { ShutdownDatabaseConnection(instance()) }
bind<SimpleJWT>(tag = "auth") with singleton { simpleJwtFactory(instance<Config>().jwt.auth) }
bind<SimpleJWT>(tag = "refresh") with singleton { simpleJwtFactory(instance<Config>().jwt.refresh) }
bind() from singleton { LoggerFactory.getLogger("Application") }
bind() from singleton { dataSourceFactory(instance<Config>().database) }
bind() from singleton { databaseFactory(instance()) }
bind() from singleton { Migration(instance()) }
bind<PasswordHash>() with singleton { BcryptPasswordHash() } bind<PasswordHash>() with singleton { BcryptPasswordHash() }
} }

View File

@ -1,8 +1,6 @@
package be.vandewalleh package be.vandewalleh
import be.vandewalleh.features.Config import be.vandewalleh.extensions.ApplicationBuilder
import be.vandewalleh.features.loadFeatures
import be.vandewalleh.migrations.Migration
import be.vandewalleh.routing.noteRoutes import be.vandewalleh.routing.noteRoutes
import be.vandewalleh.routing.tagsRoute import be.vandewalleh.routing.tagsRoute
import be.vandewalleh.routing.userRoutes import be.vandewalleh.routing.userRoutes
@ -14,23 +12,20 @@ import io.ktor.server.netty.*
import org.kodein.di.Kodein import org.kodein.di.Kodein
import org.kodein.di.description import org.kodein.di.description
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
import org.kodein.di.generic.with
import org.slf4j.Logger import org.slf4j.Logger
import java.util.concurrent.TimeUnit
fun main(args: Array<String>) { fun main() {
val kodein = Kodein {
val kodein = Kodein{
import(mainModule) import(mainModule)
constant("config file") with "/application.prod.yaml" // FIXME
} }
val config by kodein.instance<Config>() val config by kodein.instance<Config>()
val logger by kodein.instance<Logger>() val logger by kodein.instance<Logger>()
logger.info("Running application with configuration $config") logger.info("Running application with configuration $config")
logger.debug("Kodein bindings\n${kodein.container.tree.bindings.description()}") logger.debug("Kodein bindings\n${kodein.container.tree.bindings.description()}")
val migration by kodein.instance<Migration>()
migration.migrate()
serve(kodein) serve(kodein)
} }
@ -47,12 +42,19 @@ fun serve(kodein: Kodein) {
port = config.server.port port = config.server.port
} }
} }
embeddedServer(Netty, env).start(wait = true) with(embeddedServer(Netty, env)) {
addShutdownHook { stop(3, 5, TimeUnit.SECONDS) }
start(wait = true)
}
} }
fun Application.module(kodein: Kodein) { fun Application.module(kodein: Kodein) {
loadFeatures(kodein) val builders: Set<ApplicationBuilder> by kodein.instance()
builders.forEach {
it.builder(this)
}
routing { routing {
route("/user") { route("/user") {

View File

@ -1,19 +1,17 @@
package be.vandewalleh.auth package be.vandewalleh.auth
import be.vandewalleh.extensions.ApplicationBuilder
import io.ktor.application.* import io.ktor.application.*
import io.ktor.auth.* import io.ktor.auth.*
import io.ktor.auth.jwt.* import io.ktor.auth.jwt.*
import org.kodein.di.Kodein
import org.kodein.di.generic.instance
fun Application.authenticationModule(kodein: Kodein) { class AuthenticationModule(authJwt: SimpleJWT) : ApplicationBuilder({
install(Authentication) { install(Authentication) {
jwt { jwt {
val simpleJwt by kodein.instance<SimpleJWT>(tag = "auth") verifier(authJwt.verifier)
verifier(simpleJwt.verifier)
validate { validate {
UserDbIdPrincipal(it.payload.getClaim("id").asInt()) UserIdPrincipal(it.payload.getClaim("id").asInt())
} }
} }
} }
} })

View File

@ -1,5 +0,0 @@
package be.vandewalleh.auth
import io.ktor.auth.Principal
data class UserDbIdPrincipal(val id: Int) : Principal

View File

@ -0,0 +1,9 @@
package be.vandewalleh.auth
import io.ktor.auth.*
/**
* Represents a simple user's principal identified by [id]
* @property id of the user
*/
data class UserIdPrincipal(val id: Int) : Principal

View File

@ -1,6 +1,6 @@
package be.vandewalleh.extensions package be.vandewalleh.extensions
import be.vandewalleh.auth.UserDbIdPrincipal import be.vandewalleh.auth.UserIdPrincipal
import io.ktor.application.* import io.ktor.application.*
import io.ktor.auth.* import io.ktor.auth.*
import io.ktor.http.* import io.ktor.http.*
@ -17,4 +17,4 @@ suspend fun ApplicationCall.respondStatus(status: HttpStatusCode) {
/** /**
* @return the userId for the currently authenticated user * @return the userId for the currently authenticated user
*/ */
fun ApplicationCall.authenticatedUserId() = principal<UserDbIdPrincipal>()!!.id fun ApplicationCall.authenticatedUserId() = principal<UserIdPrincipal>()!!.id

View File

@ -1,11 +1,7 @@
package be.vandewalleh.extensions package be.vandewalleh.extensions
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
fun <T> ioAsync(block: suspend CoroutineScope.() -> T): Deferred<T> {
return CoroutineScope(Dispatchers.IO).async(block = block)
}
suspend inline fun <T> launchIo(crossinline block: () -> T): T = suspend inline fun <T> launchIo(crossinline block: () -> T): T =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {

View File

@ -0,0 +1,8 @@
package be.vandewalleh.extensions
import io.ktor.application.*
import io.ktor.routing.*
abstract class RoutingBuilder(val builder: Routing.() -> Unit)
abstract class ApplicationBuilder(val builder: Application.() -> Unit)

View File

@ -0,0 +1,7 @@
package be.vandewalleh.factories
import be.vandewalleh.Config
import com.sksamuel.hoplite.ConfigLoader
fun configurationFactory() =
ConfigLoader().loadConfigOrThrow<Config>("/application.yaml")

View File

@ -0,0 +1,20 @@
package be.vandewalleh.factories
import be.vandewalleh.DatabaseConfig
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
fun dataSourceFactory(config: DatabaseConfig): HikariDataSource {
val host = config.host
val port = config.port
val name = config.name
val hikariConfig = HikariConfig().apply {
jdbcUrl = "jdbc:mariadb://$host:$port/$name"
username = config.username
password = config.password.value
connectionTimeout = 3000 // 3 seconds
}
return HikariDataSource(hikariConfig)
}

View File

@ -0,0 +1,6 @@
package be.vandewalleh.factories
import me.liuwj.ktorm.database.*
import javax.sql.DataSource
fun databaseFactory(dataSource: DataSource) = Database.connect(dataSource)

View File

@ -0,0 +1,6 @@
package be.vandewalleh.factories
import be.vandewalleh.Jwt
import be.vandewalleh.auth.SimpleJWT
fun simpleJwtFactory(jwt: Jwt) = SimpleJWT(jwt.secret.value, jwt.validity, jwt.unit)

View File

@ -1,62 +0,0 @@
package be.vandewalleh.features
import be.vandewalleh.auth.SimpleJWT
import com.sksamuel.hoplite.ConfigLoader
import com.sksamuel.hoplite.Masked
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.singleton
import java.util.concurrent.TimeUnit
import javax.sql.DataSource
/**
* [Kodein] controller module containing the app configuration
*/
val configurationModule = Kodein.Module(name = "Configuration") {
bind() from singleton { ConfigLoader().loadConfigOrThrow<Config>("/application.yaml") }
bind<SimpleJWT>(tag = "auth") with singleton { configureAuthJwt(this.kodein) }
bind<SimpleJWT>(tag = "refresh") with singleton { configureRefreshJwt(this.kodein) }
bind<DataSource>() with singleton { configureDatasource(this.kodein) }
}
data class DatabaseConfig(val host: String, val port: Int, val name: String, val username: String, val password: Masked)
data class ServerConfig(val host: String, val port: Int, val cors: Boolean)
data class JwtConfig(val auth: Jwt, val refresh: Jwt)
data class Jwt(val validity: Long, val unit: TimeUnit, val secret: Masked)
data class Config(val database: DatabaseConfig, val server: ServerConfig, val jwt: JwtConfig)
private fun configureAuthJwt(kodein: Kodein): SimpleJWT {
val config by kodein.instance<Config>()
val jwtSecret = config.jwt.auth.secret
val authConfig = config.jwt.auth
return SimpleJWT(jwtSecret.value, authConfig.validity, authConfig.unit)
}
private fun configureRefreshJwt(kodein: Kodein): SimpleJWT {
val config by kodein.instance<Config>()
val jwtSecret = config.jwt.refresh.secret
val refreshConfig = config.jwt.auth
return SimpleJWT(jwtSecret.value, refreshConfig.validity, refreshConfig.unit)
}
private fun configureDatasource(kodein: Kodein): DataSource {
val config by kodein.instance<Config>()
val dbConfig = config.database
val host = dbConfig.host
val port = dbConfig.port
val name = dbConfig.name
val hikariConfig = HikariConfig().apply {
jdbcUrl = "jdbc:mariadb://$host:$port/$name"
username = dbConfig.username
password = dbConfig.password.value
connectionTimeout = 3000 // 3 seconds
}
return HikariDataSource(hikariConfig)
}

View File

@ -1,5 +1,6 @@
package be.vandewalleh.features package be.vandewalleh.features
import be.vandewalleh.extensions.ApplicationBuilder
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.util.StdDateFormat import com.fasterxml.jackson.databind.util.StdDateFormat
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
@ -8,7 +9,7 @@ import io.ktor.features.*
import io.ktor.jackson.* import io.ktor.jackson.*
import me.liuwj.ktorm.jackson.* import me.liuwj.ktorm.jackson.*
fun Application.contentNegotiationFeature() { class ContentNegotiationFeature : ApplicationBuilder({
install(ContentNegotiation) { install(ContentNegotiation) {
jackson { jackson {
registerModule(KtormModule()) registerModule(KtormModule())
@ -17,4 +18,4 @@ fun Application.contentNegotiationFeature() {
dateFormat = StdDateFormat() dateFormat = StdDateFormat()
} }
} }
} })

View File

@ -1,14 +1,17 @@
package be.vandewalleh.features package be.vandewalleh.features
import be.vandewalleh.extensions.ApplicationBuilder
import io.ktor.application.* import io.ktor.application.*
import io.ktor.features.* import io.ktor.features.*
import io.ktor.http.* import io.ktor.http.*
fun Application.corsFeature() { class CorsFeature(enabled: Boolean) : ApplicationBuilder({
install(CORS) { if (enabled) {
anyHost() install(CORS) {
header(HttpHeaders.ContentType) anyHost()
header(HttpHeaders.Authorization) header(HttpHeaders.ContentType)
methods.add(HttpMethod.Delete) header(HttpHeaders.Authorization)
methods.add(HttpMethod.Delete)
}
} }
} })

View File

@ -1,5 +1,6 @@
package be.vandewalleh.features package be.vandewalleh.features
import be.vandewalleh.extensions.ApplicationBuilder
import io.ktor.application.* import io.ktor.application.*
import io.ktor.features.* import io.ktor.features.*
import io.ktor.http.* import io.ktor.http.*
@ -7,7 +8,8 @@ import io.ktor.response.*
import io.ktor.utils.io.errors.* import io.ktor.utils.io.errors.*
import java.sql.SQLTransientConnectionException import java.sql.SQLTransientConnectionException
fun Application.handleErrors() {
class ErrorHandler : ApplicationBuilder({
install(StatusPages) { install(StatusPages) {
jacksonErrors() jacksonErrors()
@ -24,7 +26,7 @@ fun Application.handleErrors() {
call.respond(HttpStatusCode.InternalServerError, error) call.respond(HttpStatusCode.InternalServerError, error)
} }
} }
} })
class ValidationException(val error: String) : RuntimeException() class ValidationException(val error: String) : RuntimeException()
class ErrorResponse(val error: String) class ErrorResponse(val error: String)

View File

@ -1,16 +0,0 @@
package be.vandewalleh.features
import be.vandewalleh.auth.authenticationModule
import io.ktor.application.*
import org.kodein.di.Kodein
import org.kodein.di.generic.instance
fun Application.loadFeatures(kodein: Kodein) {
val config by kodein.instance<Config>()
if (config.server.cors) {
corsFeature()
}
contentNegotiationFeature()
authenticationModule(kodein)
handleErrors()
}

View File

@ -0,0 +1,28 @@
package be.vandewalleh.features
import be.vandewalleh.extensions.ApplicationBuilder
import io.ktor.application.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.flywaydb.core.Flyway
import javax.sql.DataSource
class MigrationHook(migration: Migration) : ApplicationBuilder({
environment.monitor.subscribe(ApplicationStarted) {
CoroutineScope(Dispatchers.IO).launch {
migration.migrate()
}
}
})
class Migration(private val dataSource: DataSource) {
fun migrate() {
Flyway.configure()
.dataSource(dataSource)
.baselineOnMigrate(true)
.load()
.migrate()
}
}

View File

@ -0,0 +1,13 @@
package be.vandewalleh.features
import be.vandewalleh.extensions.ApplicationBuilder
import com.zaxxer.hikari.HikariDataSource
import io.ktor.application.*
class ShutdownDatabaseConnection(hikariDataSource: HikariDataSource) : ApplicationBuilder({
environment.monitor.subscribe(ApplicationStopPreparing) {
if (!hikariDataSource.isClosed) {
hikariDataSource.close()
}
}
})

View File

@ -1,20 +0,0 @@
package be.vandewalleh.migrations
import org.flywaydb.core.Flyway
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.generic.instance
import javax.sql.DataSource
class Migration(override val kodein: Kodein) : KodeinAware {
fun migrate() {
val dataSource by instance<DataSource>()
val flyway = Flyway.configure()
.dataSource(dataSource)
.baselineOnMigrate(true)
.load()
flyway.migrate()
}
}

View File

@ -1,10 +1,10 @@
package be.vandewalleh.routing package be.vandewalleh.routing
import be.vandewalleh.auth.SimpleJWT import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.auth.UserDbIdPrincipal import be.vandewalleh.auth.UserIdPrincipal
import be.vandewalleh.auth.UsernamePasswordCredential import be.vandewalleh.auth.UsernamePasswordCredential
import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.extensions.authenticatedUserId import be.vandewalleh.extensions.authenticatedUserId
import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.features.PasswordHash import be.vandewalleh.features.PasswordHash
import be.vandewalleh.services.UserService import be.vandewalleh.services.UserService
import be.vandewalleh.validation.receiveValidated import be.vandewalleh.validation.receiveValidated
@ -87,7 +87,7 @@ fun Route.userRoutes(kodein: Kodein) {
} }
get("/me") { get("/me") {
val id = call.principal<UserDbIdPrincipal>()!!.id val id = call.principal<UserIdPrincipal>()!!.id
val info = userService.find(id) val info = userService.find(id)
if (info != null) call.respond(mapOf("user" to info)) if (info != null) call.respond(mapOf("user" to info))
else call.respondStatus(HttpStatusCode.Unauthorized) else call.respondStatus(HttpStatusCode.Unauthorized)

View File

@ -1,45 +1,38 @@
package be.vandewalleh.services package be.vandewalleh.services
import be.vandewalleh.entities.Note import be.vandewalleh.entities.Note
import be.vandewalleh.extensions.ioAsync
import be.vandewalleh.extensions.launchIo import be.vandewalleh.extensions.launchIo
import be.vandewalleh.tables.Notes import be.vandewalleh.tables.Notes
import be.vandewalleh.tables.Tags import be.vandewalleh.tables.Tags
import me.liuwj.ktorm.database.* import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.* import me.liuwj.ktorm.entity.*
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.generic.instance
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
/** /**
* service to handle database queries at the Notes level. * service to handle database queries at the Notes level.
*/ */
class NoteService(override val kodein: Kodein) : KodeinAware { class NoteService(private val db: Database) {
private val db by instance<Database>()
/** /**
* returns a list of [Note] associated with the userId * returns a list of [Note] associated with the userId
*/ */
suspend fun findAll(userId: Int): List<Note> { suspend fun findAll(userId: Int, limit: Int = 20, offset: Int = 0): List<Note> = launchIo {
val notes = launchIo { val notes = db.sequenceOf(Notes, withReferences = false)
db.sequenceOf(Notes, withReferences = false) .filterColumns { it.columns - it.userId }
.filterColumns { listOf(it.uuid, it.title, it.updatedAt) } .filter { it.userId eq userId }
.filter { it.userId eq userId } .sortedByDescending { it.updatedAt }
.sortedByDescending { it.updatedAt } .take(limit).drop(offset)
.toList() .toList()
}
if (notes.isEmpty()) return emptyList() if (notes.isEmpty()) return@launchIo emptyList()
val allTags = launchIo { val allTags =
db.sequenceOf(Tags, withReferences = false) db.sequenceOf(Tags, withReferences = false)
.filterColumns { listOf(it.noteUuid, it.name) } .filterColumns { listOf(it.noteUuid, it.name) }
.filter { it.noteUuid inList notes.map { note -> note.uuid } } .filter { it.noteUuid inList notes.map { note -> note.uuid } }
.toList() .toList()
}
val tagsByUuid = allTags.groupByTo(HashMap(), { it.note.uuid }, { it.name }) val tagsByUuid = allTags.groupByTo(HashMap(), { it.note.uuid }, { it.name })
@ -48,7 +41,7 @@ class NoteService(override val kodein: Kodein) : KodeinAware {
if (tags != null) it.tags = tags if (tags != null) it.tags = tags
} }
return notes notes
} }
suspend fun exists(userId: Int, uuid: UUID) = launchIo { suspend fun exists(userId: Int, uuid: UUID) = launchIo {
@ -79,24 +72,20 @@ class NoteService(override val kodein: Kodein) : KodeinAware {
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
suspend fun find(userId: Int, noteUuid: UUID): Note? { suspend fun find(userId: Int, noteUuid: UUID): Note? = launchIo {
val deferredNote = ioAsync { val note =
db.sequenceOf(Notes, withReferences = false) db.sequenceOf(Notes, withReferences = false)
.filterColumns { it.columns - it.userId } .filterColumns { it.columns - it.userId }
.filter { it.uuid eq noteUuid } .filter { it.uuid eq noteUuid }
.find { it.userId eq userId } .find { it.userId eq userId }
} ?: return@launchIo null
val deferredTags = ioAsync { val tags =
db.sequenceOf(Tags, withReferences = false) db.sequenceOf(Tags, withReferences = false)
.filter { it.noteUuid eq noteUuid } .filter { it.noteUuid eq noteUuid }
.mapColumns { it.name } as List<String> .mapColumns { it.name } as List<String>
}
val note = deferredNote.await() ?: return null note.also { it.tags = tags }
val tags = deferredTags.await()
return note.also { it.tags = tags }
} }
suspend fun updateNote(userId: Int, note: Note): Boolean = launchIo { suspend fun updateNote(userId: Int, note: Note): Boolean = launchIo {

View File

@ -1,14 +0,0 @@
package be.vandewalleh.services
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.singleton
/**
* [Kodein] controller module containing the app services
*/
val serviceModule = Kodein.Module(name = "Services") {
bind<NoteService>() with singleton { NoteService(this.kodein) }
bind<UserService>() with singleton { UserService(this.kodein) }
}

View File

@ -7,18 +7,12 @@ import be.vandewalleh.tables.Users
import me.liuwj.ktorm.database.* import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.* import me.liuwj.ktorm.entity.*
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.generic.instance
import java.sql.SQLIntegrityConstraintViolationException import java.sql.SQLIntegrityConstraintViolationException
/** /**
* service to handle database queries for users. * service to handle database queries for users.
*/ */
class UserService(override val kodein: Kodein) : KodeinAware { class UserService(private val db: Database, private val passwordHash: PasswordHash) {
private val db by instance<Database>()
private val passwordHash by instance<PasswordHash>()
/** /**
* returns a user from it's username if found or null * returns a user from it's username if found or null

View File

@ -4,7 +4,6 @@ import am.ik.yavi.builder.ValidatorBuilder
import am.ik.yavi.builder.konstraint import am.ik.yavi.builder.konstraint
import am.ik.yavi.core.Validator import am.ik.yavi.core.Validator
import be.vandewalleh.entities.Note import be.vandewalleh.entities.Note
import be.vandewalleh.entities.User
val noteValidator: Validator<Note> = ValidatorBuilder.of<Note>() val noteValidator: Validator<Note> = ValidatorBuilder.of<Note>()
.konstraint(Note::title) { .konstraint(Note::title) {

View File

@ -1,8 +1,8 @@
package integration.routing package integration.routing
import be.vandewalleh.Config
import be.vandewalleh.auth.SimpleJWT import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.entities.User import be.vandewalleh.entities.User
import be.vandewalleh.features.Config
import be.vandewalleh.features.PasswordHash import be.vandewalleh.features.PasswordHash
import be.vandewalleh.mainModule import be.vandewalleh.mainModule
import be.vandewalleh.module import be.vandewalleh.module

View File

@ -4,8 +4,8 @@ import am.ik.yavi.builder.ValidatorBuilder
import am.ik.yavi.core.CustomConstraint import am.ik.yavi.core.CustomConstraint
import am.ik.yavi.core.Validator import am.ik.yavi.core.Validator
import be.vandewalleh.entities.Note import be.vandewalleh.entities.Note
import be.vandewalleh.features.Migration
import be.vandewalleh.mainModule import be.vandewalleh.mainModule
import be.vandewalleh.migrations.Migration
import be.vandewalleh.services.NoteService import be.vandewalleh.services.NoteService
import be.vandewalleh.services.UserService import be.vandewalleh.services.UserService
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
@ -34,7 +34,7 @@ class NoteServiceTest {
private val kodein = Kodein { private val kodein = Kodein {
import(mainModule, allowOverride = true) import(mainModule, allowOverride = true)
bind<DataSource>(overrides = true) with singleton { mariadb.datasource() } bind(overrides = true) from singleton { mariadb.datasource() }
} }
init { init {
@ -216,7 +216,7 @@ class NoteServiceTest {
disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT) disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
dateFormat = StdDateFormat() dateFormat = StdDateFormat()
} }
val note: Note = objectMapper.readValue("""{"uuid": "c6d80-5fe6-4a30-b034-da63f6663c2c"}""") val note: Note = objectMapper.readValue("""{"uuid": "2007e4d7-2986-4188-bde1-b99916d94bad"}""")
println(note.uuid) println(note.uuid)
println(note.uuid::class.qualifiedName) println(note.uuid::class.qualifiedName)
println(note.uuid.leastSignificantBits) println(note.uuid.leastSignificantBits)

View File

@ -1,8 +1,9 @@
package integration.services package integration.services
import be.vandewalleh.features.Migration
import be.vandewalleh.mainModule import be.vandewalleh.mainModule
import be.vandewalleh.migrations.Migration
import be.vandewalleh.services.UserService import be.vandewalleh.services.UserService
import com.zaxxer.hikari.HikariDataSource
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.amshove.kluent.* import org.amshove.kluent.*
import org.junit.jupiter.api.* import org.junit.jupiter.api.*
@ -22,17 +23,16 @@ class UserServiceTest {
private val kodein = Kodein { private val kodein = Kodein {
import(mainModule, allowOverride = true) import(mainModule, allowOverride = true)
bind<DataSource>(overrides = true) with singleton { mariadb.datasource() } bind(overrides = true) from singleton { mariadb.datasource() }
}
private val migration by kodein.instance<Migration>()
init {
migration.migrate()
} }
private val userService by kodein.instance<UserService>() private val userService by kodein.instance<UserService>()
init {
val migration by kodein.instance<Migration>()
migration.migrate()
}
@Test @Test
@Order(1) @Order(1)
fun `test create user`() { fun `test create user`() {

View File

@ -6,7 +6,7 @@ import org.testcontainers.containers.MariaDBContainer
import javax.sql.DataSource import javax.sql.DataSource
class KMariadbContainer : MariaDBContainer<KMariadbContainer>() { class KMariadbContainer : MariaDBContainer<KMariadbContainer>() {
fun datasource() : DataSource { fun datasource() : HikariDataSource {
val hikariConfig = HikariConfig().apply { val hikariConfig = HikariConfig().apply {
jdbcUrl = this@KMariadbContainer.jdbcUrl jdbcUrl = this@KMariadbContainer.jdbcUrl
username = this@KMariadbContainer.username username = this@KMariadbContainer.username