Merge branch 'feature/config'

This commit is contained in:
Hubert Van De Walle 2020-04-29 20:04:15 +02:00
commit 01636083e1
8 changed files with 119 additions and 68 deletions

View File

@ -18,6 +18,7 @@
<flyway_version>6.3.3</flyway_version>
<javajwt_version>3.10.2</javajwt_version>
<jbcrypt_version>0.4</jbcrypt_version>
<hoplite_version>1.2.2</hoplite_version>
<kotlin.code.style>official</kotlin.code.style>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -149,6 +150,16 @@
<artifactId>HikariCP</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.sksamuel.hoplite</groupId>
<artifactId>hoplite-core</artifactId>
<version>${hoplite_version}</version>
</dependency>
<dependency>
<groupId>com.sksamuel.hoplite</groupId>
<artifactId>hoplite-yaml</artifactId>
<version>${hoplite_version}</version>
</dependency>
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>

View File

@ -1,22 +0,0 @@
ktor {
deployment {
port = 8081
port = ${?PORT}
}
application {
modules = [ be.vandewalleh.NotesApplicationKt.module ]
}
}
database {
host = "127.0.0.1"
port = "3306"
name = "Notes"
user = "test"
password = "test"
}
jwt {
secret = "thisisasecret"
secret = ${?SECRET}
}

View File

@ -0,0 +1,22 @@
env: staging
database:
host: 127.0.0.1
port: 3306
name: Notes
username: test
password: test
server:
host: 0.0.0.0
port: 8081
jwt:
secret: ${random.string(25)}
auth:
validity: 1
unit: HOURS
refresh:
validity: 15
unit: DAYS

View File

@ -1,42 +1,64 @@
package be.vandewalleh
import be.vandewalleh.features.configurationFeature
import be.vandewalleh.features.Config
import be.vandewalleh.features.configurationModule
import be.vandewalleh.features.features
import be.vandewalleh.features.loadFeatures
import be.vandewalleh.migrations.Migration
import be.vandewalleh.routing.registerRoutes
import be.vandewalleh.services.serviceModule
import io.ktor.application.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import me.liuwj.ktorm.database.*
import org.kodein.di.Kodein
import org.kodein.di.description
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.singleton
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import javax.sql.DataSource
import kotlin.system.exitProcess
lateinit var kodein: Kodein
@Suppress("unused") // Referenced in application.conf
fun Application.module() {
// must be first to be loaded
configurationFeature()
kodein = Kodein {
import(configurationModule)
import(serviceModule)
bind<Migration>() with singleton { Migration(this.kodein) }
bind<Database>() with singleton { Database.Companion.connect(this.instance<DataSource>()) }
}
features()
log.debug(kodein.container.tree.bindings.description())
val kodein = Kodein {
import(serviceModule)
import(configurationModule)
bind<Logger>() with singleton { LoggerFactory.getLogger("Application") }
bind<Migration>() with singleton { Migration(this.kodein) }
bind<Database>() with singleton { Database.connect(this.instance<DataSource>()) }
}
fun main() {
val config by kodein.instance<Config>()
val logger by kodein.instance<Logger>()
logger.info("Running application with configuration $config")
val migration by kodein.instance<Migration>()
migration.migrate()
serve(kodein)
}
fun serve(kodein: Kodein) {
val config by kodein.instance<Config>()
val logger by kodein.instance<Logger>()
val env = applicationEngineEnvironment {
module {
module()
}
log = logger
connector {
host = config.server.host
port = config.server.port
}
}
embeddedServer(Netty, env).start(wait = true)
}
fun Application.module() {
loadFeatures()
log.debug(kodein.container.tree.bindings.description())
routing {
registerRoutes(kodein)

View File

@ -1,42 +1,61 @@
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 io.ktor.application.*
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] configuration module
* [Kodein] controller module containing the app configuration
*/
lateinit var configurationModule: Kodein.Module
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) }
}
fun Application.configurationFeature() {
val dataSource: DataSource = with(environment.config) {
val host = property("database.host").getString()
val port = property("database.port").getString()
val name = property("database.name").getString()
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)
data class JwtConfig(val secret: Masked, val auth: JwtValidity, val refresh: JwtValidity)
data class JwtValidity(val validity: Long, val unit: TimeUnit)
data class Config(val database: DatabaseConfig, val server: ServerConfig, val jwt: JwtConfig)
val hikariConfig = HikariConfig().apply {
jdbcUrl = "jdbc:mariadb://$host:$port/$name"
username = this@with.property("database.user").getString()
password = this@with.property("database.password").getString()
}
HikariDataSource(hikariConfig)
private fun configureAuthJwt(kodein: Kodein): SimpleJWT {
val config by kodein.instance<Config>()
val jwtSecret = config.jwt.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.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
}
val jwtSecret = environment.config.property("jwt.secret").getString()
val authSimpleJwt = SimpleJWT(jwtSecret, 1, TimeUnit.HOURS)
val refreshSimpleJwt = SimpleJWT(jwtSecret, 7, TimeUnit.DAYS)
configurationModule = Kodein.Module("Configuration") {
bind<DataSource>() with instance(dataSource)
bind<SimpleJWT>(tag = "auth") with instance(authSimpleJwt)
bind<SimpleJWT>(tag = "refresh") with instance(refreshSimpleJwt)
}
return HikariDataSource(hikariConfig)
}

View File

@ -1,6 +1,5 @@
package be.vandewalleh.features
import com.fasterxml.jackson.databind.SerializationFeature
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.jackson.*

View File

@ -8,7 +8,7 @@ import io.ktor.utils.io.errors.*
fun Application.handleErrors() {
install(StatusPages) {
exception<IOException> { _ ->
exception<IOException> {
call.respond(HttpStatusCode.BadRequest)
}
}

View File

@ -3,7 +3,7 @@ package be.vandewalleh.features
import be.vandewalleh.auth.authenticationModule
import io.ktor.application.*
fun Application.features() {
fun Application.loadFeatures() {
corsFeature()
contentNegotiationFeature()
authenticationModule()