diff --git a/api/pom.xml b/api/pom.xml index 619ad87..e3e0a43 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -18,6 +18,7 @@ 6.3.3 3.10.2 0.4 + 1.2.2 official UTF-8 @@ -149,6 +150,16 @@ HikariCP 3.4.2 + + com.sksamuel.hoplite + hoplite-core + ${hoplite_version} + + + com.sksamuel.hoplite + hoplite-yaml + ${hoplite_version} + com.github.javafaker javafaker diff --git a/api/resources/application.conf b/api/resources/application.conf deleted file mode 100644 index 4b934ce..0000000 --- a/api/resources/application.conf +++ /dev/null @@ -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} -} \ No newline at end of file diff --git a/api/resources/application.yaml b/api/resources/application.yaml new file mode 100644 index 0000000..388f21a --- /dev/null +++ b/api/resources/application.yaml @@ -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 + diff --git a/api/src/NotesApplication.kt b/api/src/NotesApplication.kt index 52ce1f8..04805fe 100644 --- a/api/src/NotesApplication.kt +++ b/api/src/NotesApplication.kt @@ -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() with singleton { Migration(this.kodein) } - bind() with singleton { Database.Companion.connect(this.instance()) } - } - - features() - - log.debug(kodein.container.tree.bindings.description()) +val kodein = Kodein { + import(serviceModule) + import(configurationModule) + bind() with singleton { LoggerFactory.getLogger("Application") } + bind() with singleton { Migration(this.kodein) } + bind() with singleton { Database.connect(this.instance()) } +} +fun main() { + val config by kodein.instance() + val logger by kodein.instance() + logger.info("Running application with configuration $config") val migration by kodein.instance() migration.migrate() + serve(kodein) +} + +fun serve(kodein: Kodein) { + val config by kodein.instance() + val logger by kodein.instance() + 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) diff --git a/api/src/features/ConfigurationFeature.kt b/api/src/features/ConfigurationFeature.kt index 67aae98..6dbad66 100644 --- a/api/src/features/ConfigurationFeature.kt +++ b/api/src/features/ConfigurationFeature.kt @@ -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("/application.yaml") } + bind(tag = "auth") with singleton { configureAuthJwt(this.kodein) } + bind(tag = "refresh") with singleton { configureRefreshJwt(this.kodein) } + bind() 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() + 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() + 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() + 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() with instance(dataSource) - bind(tag = "auth") with instance(authSimpleJwt) - bind(tag = "refresh") with instance(refreshSimpleJwt) - } + return HikariDataSource(hikariConfig) } \ No newline at end of file diff --git a/api/src/features/ContentNegotiationFeature.kt b/api/src/features/ContentNegotiationFeature.kt index 8906914..df99c0a 100644 --- a/api/src/features/ContentNegotiationFeature.kt +++ b/api/src/features/ContentNegotiationFeature.kt @@ -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.* diff --git a/api/src/features/ErrorFeature.kt b/api/src/features/ErrorFeature.kt index 6ca34cc..c901e7e 100644 --- a/api/src/features/ErrorFeature.kt +++ b/api/src/features/ErrorFeature.kt @@ -8,7 +8,7 @@ import io.ktor.utils.io.errors.* fun Application.handleErrors() { install(StatusPages) { - exception { _ -> + exception { call.respond(HttpStatusCode.BadRequest) } } diff --git a/api/src/features/Features.kt b/api/src/features/Features.kt index c57e449..e2a4eb3 100644 --- a/api/src/features/Features.kt +++ b/api/src/features/Features.kt @@ -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()