Load configuration with micronaut

This commit is contained in:
Hubert Van De Walle 2020-11-03 14:55:37 +01:00
parent 4f395d254d
commit 941380ad16
19 changed files with 68 additions and 137 deletions

View File

@ -1,10 +1,6 @@
## can be generated with `openssl rand -base64 32` # mariadb
JWT_SECRET=
#
## can be generated with `openssl rand -base64 32`
MYSQL_ROOT_PASSWORD= MYSQL_ROOT_PASSWORD=
#
## can be generated with `openssl rand -base64 32`
MYSQL_PASSWORD= MYSQL_PASSWORD=
# password should be the same as mysql_password # simplenotes
PASSWORD= DB_PASSWORD=
JWT_SECRET=

View File

@ -15,5 +15,4 @@
## Configuration ## Configuration
The app is configured with environments variables. The app is configured with environments variables.
If no match is found within the env, a default value is read from a properties file in /app/src/main/resources/application.properties. If no match is found within the env, a default value is read from a yaml file in simplenotes-app/src/main/resources/application.yaml.
Don't use the default values for secrets ! Every value inside *.env.dist* should be changed.

View File

@ -32,13 +32,13 @@ services:
- .env - .env
environment: environment:
- TZ=Europe/Brussels - TZ=Europe/Brussels
- HOST=0.0.0.0 - SERVER_HOST=0.0.0.0
- JDBCURL=jdbc:mariadb://db:3306/simplenotes - DB_JDBC_URL=jdbc:mariadb://db:3306/simplenotes
- DRIVERCLASSNAME=org.mariadb.jdbc.Driver - DB_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver
- USERNAME=simplenotes - DB_USERNAME=simplenotes
# .env: # .env:
# - JWT_SECRET # - JWT_SECRET
# - PASSWORD # - DB_PASSWORD
ports: ports:
- 127.0.0.1:8080:8080 - 127.0.0.1:8080:8080
healthcheck: healthcheck:

View File

@ -30,9 +30,6 @@ dependencies {
implementation(Libs.micronaut) implementation(Libs.micronaut)
kapt(Libs.micronautProcessor) kapt(Libs.micronautProcessor)
testImplementation(Libs.micronaut)
kaptTest(Libs.micronautProcessor)
testImplementation(Libs.junit) testImplementation(Libs.junit)
testImplementation(Libs.assertJ) testImplementation(Libs.assertJ)
testImplementation(Libs.http4kTestingHamkrest) testImplementation(Libs.http4kTestingHamkrest)

View File

@ -1,13 +1,13 @@
package be.simplenotes.app package be.simplenotes.app
import io.micronaut.context.annotation.Context
import org.http4k.server.Http4kServer import org.http4k.server.Http4kServer
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import javax.annotation.PostConstruct import javax.annotation.PostConstruct
import javax.annotation.PreDestroy import javax.annotation.PreDestroy
import javax.inject.Singleton
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
@Context @Singleton
class Server( class Server(
private val config: SimpleNotesServerConfig, private val config: SimpleNotesServerConfig,
private val http4kServer: Http4kServer, private val http4kServer: Http4kServer,
@ -17,7 +17,7 @@ class Server(
@PostConstruct @PostConstruct
fun start(): Server { fun start(): Server {
http4kServer.start() http4kServer.start()
logger.info("Listening on http://${config.host}:${config.port}") logger.info("Listening on http://${config.host}:${http4kServer.port()}")
return this return this
} }

View File

@ -1,12 +1,10 @@
package be.simplenotes.app package be.simplenotes.app
import io.micronaut.context.ApplicationContext import io.micronaut.context.ApplicationContext
import java.lang.Runtime.getRuntime
fun main() { fun main() {
val ctx = ApplicationContext.run().start() val ctx = ApplicationContext.run()
Runtime.getRuntime().addShutdownHook( ctx.createBean(Server::class.java)
Thread { getRuntime().addShutdownHook(Thread { ctx.stop() })
ctx.stop()
}
)
} }

View File

@ -14,4 +14,5 @@
<logger name="com.zaxxer.hikari" level="INFO"/> <logger name="com.zaxxer.hikari" level="INFO"/>
<logger name="org.flywaydb.core" level="INFO"/> <logger name="org.flywaydb.core" level="INFO"/>
<logger name="io.micronaut" level="INFO"/> <logger name="io.micronaut" level="INFO"/>
<logger name="io.micronaut.context.lifecycle" level="DEBUG"/>
</configuration> </configuration>

View File

@ -1,8 +1,12 @@
package be.simplenotes.config package be.simplenotes.config
import io.micronaut.context.annotation.ConfigurationInject
import io.micronaut.context.annotation.ConfigurationProperties
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Singleton
data class DataSourceConfig( @ConfigurationProperties("db")
data class DataSourceConfig @ConfigurationInject constructor(
val jdbcUrl: String, val jdbcUrl: String,
val driverClassName: String, val driverClassName: String,
val username: String, val username: String,
@ -14,7 +18,8 @@ data class DataSourceConfig(
"username='$username', password='***', maximumPoolSize=$maximumPoolSize, connectionTimeout=$connectionTimeout)" "username='$username', password='***', maximumPoolSize=$maximumPoolSize, connectionTimeout=$connectionTimeout)"
} }
data class JwtConfig( @ConfigurationProperties("jwt")
data class JwtConfig @ConfigurationInject constructor(
val secret: String, val secret: String,
val validity: Long, val validity: Long,
val timeUnit: TimeUnit, val timeUnit: TimeUnit,
@ -22,7 +27,8 @@ data class JwtConfig(
override fun toString() = "JwtConfig(secret='***', validity=$validity, timeUnit=$timeUnit)" override fun toString() = "JwtConfig(secret='***', validity=$validity, timeUnit=$timeUnit)"
} }
data class ServerConfig( @ConfigurationProperties("server")
data class ServerConfig @ConfigurationInject constructor(
val host: String, val host: String,
val port: Int, val port: Int,
) )

View File

@ -1,47 +0,0 @@
package be.simplenotes.config
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Singleton
class ConfigLoader {
//region Config loading
private val properties: Properties = javaClass
.getResource("/application.properties")
.openStream()
.use {
Properties().apply { load(it) }
}
private val env = System.getenv()
private fun value(key: String): String =
env[key.toUpperCase().replace(".", "_")]
?: properties.getProperty(key)
?: error("Missing config key $key")
//endregion
val jwtConfig
get() = JwtConfig(
secret = value("jwt.secret"),
validity = value("jwt.validity").toLong(),
timeUnit = TimeUnit.HOURS,
)
val dataSourceConfig
get() = DataSourceConfig(
jdbcUrl = value("jdbcUrl"),
driverClassName = value("driverClassName"),
username = value("username"),
password = value("password"),
maximumPoolSize = value("maximumPoolSize").toInt(),
connectionTimeout = value("connectionTimeout").toLong()
)
val serverConfig
get() = ServerConfig(
host = value("host"),
port = value("port").toInt(),
)
}

View File

@ -1,17 +0,0 @@
package be.simplenotes.config
import io.micronaut.context.annotation.Factory
import javax.inject.Singleton
@Factory
class ConfigModule {
@Singleton
internal fun dataSourceConfig(configLoader: ConfigLoader) = configLoader.dataSourceConfig
@Singleton
internal fun jwtConfig(configLoader: ConfigLoader) = configLoader.jwtConfig
@Singleton
internal fun serverConfig(configLoader: ConfigLoader) = configLoader.serverConfig
}

View File

@ -1,12 +0,0 @@
host=localhost
port=8080
#
jdbcUrl=jdbc:h2:./notes-db;
driverClassName=org.h2.Driver
username=h2
password=
maximumPoolSize=10
connectionTimeout=3000
#
jwt.secret=PliLvfk7l4WF+cZJk66LR5Mpnh+ocbvJ2wfUCK2UCms=
jwt.validity=24

View File

@ -0,0 +1,16 @@
db:
jdbc-url: jdbc:h2:./notes-db;
driver-class-name: org.h2.Driver
username: h2
password: ''
connection-timeout: 3000
maximum-pool-size: 10
jwt:
secret: 'PliLvfk7l4WF+cZJk66LR5Mpnh+ocbvJ2wfUCK2UCms='
validity: 24
time-unit: hours
server:
host: localhost
port: 8080

View File

@ -16,16 +16,14 @@ dependencies {
implementation(Libs.hikariCP) implementation(Libs.hikariCP)
implementation(Libs.ktormCore) implementation(Libs.ktormCore)
implementation(Libs.ktormMysql) implementation(Libs.ktormMysql)
implementation(Libs.logbackClassic)
implementation(Libs.mapstruct) compileOnly(Libs.mapstruct)
kapt(Libs.mapstructProcessor) kapt(Libs.mapstructProcessor)
implementation(Libs.micronaut) implementation(Libs.micronaut)
kapt(Libs.micronautProcessor) kapt(Libs.micronautProcessor)
testImplementation(Libs.micronaut)
kaptTest(Libs.micronautProcessor)
testImplementation(Libs.junit) testImplementation(Libs.junit)
testImplementation(Libs.assertJ) testImplementation(Libs.assertJ)
testImplementation(Libs.logbackClassic) testImplementation(Libs.logbackClassic)

View File

@ -15,12 +15,6 @@ import javax.sql.DataSource
@Factory @Factory
class PersistanceModule { class PersistanceModule {
@Singleton
internal fun noteConverter() = Mappers.getMapper(NoteConverter::class.java)
@Singleton
internal fun userConverter() = Mappers.getMapper(UserConverter::class.java)
@Singleton @Singleton
internal fun database(migrations: DbMigrations, dataSource: DataSource): Database { internal fun database(migrations: DbMigrations, dataSource: DataSource): Database {
migrations.migrate() migrations.migrate()

View File

@ -9,12 +9,13 @@ import org.mapstruct.Mappings
import org.mapstruct.ReportingPolicy import org.mapstruct.ReportingPolicy
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
import javax.inject.Singleton
/** @Mapper(
* This is an abstract class because kotlin default methods in interface are not seen as default in kapt uses = [NoteEntityFactory::class, UserEntityFactory::class],
* @see [KT-25960](https://youtrack.jetbrains.com/issue/KT-25960) unmappedTargetPolicy = ReportingPolicy.IGNORE,
*/ componentModel = "jsr330"
@Mapper(uses = [NoteEntityFactory::class, UserEntityFactory::class], unmappedTargetPolicy = ReportingPolicy.IGNORE) )
internal abstract class NoteConverter { internal abstract class NoteConverter {
fun toNote(entity: NoteEntity, tags: Tags) = fun toNote(entity: NoteEntity, tags: Tags) =
@ -80,4 +81,5 @@ internal abstract class NoteConverter {
typealias Tags = List<String> typealias Tags = List<String>
@Singleton
internal class NoteEntityFactory : Entity.Factory<NoteEntity>() internal class NoteEntityFactory : Entity.Factory<NoteEntity>()

View File

@ -6,8 +6,13 @@ import be.simplenotes.types.User
import me.liuwj.ktorm.entity.Entity import me.liuwj.ktorm.entity.Entity
import org.mapstruct.Mapper import org.mapstruct.Mapper
import org.mapstruct.ReportingPolicy import org.mapstruct.ReportingPolicy
import javax.inject.Singleton
@Mapper(uses = [UserEntityFactory::class], unmappedTargetPolicy = ReportingPolicy.IGNORE) @Mapper(
uses = [UserEntityFactory::class],
unmappedTargetPolicy = ReportingPolicy.IGNORE,
componentModel = "jsr330"
)
internal interface UserConverter { internal interface UserConverter {
fun toUser(userEntity: UserEntity): User fun toUser(userEntity: UserEntity): User
fun toPersistedUser(userEntity: UserEntity): PersistedUser fun toPersistedUser(userEntity: UserEntity): PersistedUser
@ -15,4 +20,5 @@ internal interface UserConverter {
fun toEntity(user: PersistedUser): UserEntity fun toEntity(user: PersistedUser): UserEntity
} }
@Singleton
internal class UserEntityFactory : Entity.Factory<UserEntity>() internal class UserEntityFactory : Entity.Factory<UserEntity>()

View File

@ -2,23 +2,25 @@ package be.simplenotes.persistance.converters
import be.simplenotes.persistance.notes.NoteEntity import be.simplenotes.persistance.notes.NoteEntity
import be.simplenotes.types.* import be.simplenotes.types.*
import io.micronaut.context.BeanContext
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mapstruct.factory.Mappers
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
internal class NoteConverterTest { internal class NoteConverterTest {
private val ctx = BeanContext.run()
val converter = ctx.getBean(NoteConverter::class.java)
@Nested @Nested
@DisplayName("Entity -> Models") @DisplayName("Entity -> Models")
inner class EntityToModels { inner class EntityToModels {
@Test @Test
fun `convert NoteEntity to Note`() { fun `convert NoteEntity to Note`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val entity = NoteEntity { val entity = NoteEntity {
title = "title" title = "title"
markdown = "md" markdown = "md"
@ -39,7 +41,6 @@ internal class NoteConverterTest {
@Test @Test
fun `convert NoteEntity to ExportedNote`() { fun `convert NoteEntity to ExportedNote`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val entity = NoteEntity { val entity = NoteEntity {
title = "title" title = "title"
markdown = "md" markdown = "md"
@ -62,7 +63,6 @@ internal class NoteConverterTest {
@Test @Test
fun `convert NoteEntity to PersistedNoteMetadata`() { fun `convert NoteEntity to PersistedNoteMetadata`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val entity = NoteEntity { val entity = NoteEntity {
uuid = UUID.randomUUID() uuid = UUID.randomUUID()
title = "title" title = "title"
@ -89,7 +89,6 @@ internal class NoteConverterTest {
@Test @Test
fun `convert Note to NoteEntity`() { fun `convert Note to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val note = Note(NoteMetadata("title", emptyList()), "md", "html") val note = Note(NoteMetadata("title", emptyList()), "md", "html")
val entity = converter.toEntity(note, UUID.randomUUID(), 2, LocalDateTime.MIN) val entity = converter.toEntity(note, UUID.randomUUID(), 2, LocalDateTime.MIN)
@ -103,7 +102,6 @@ internal class NoteConverterTest {
@Test @Test
fun `convert PersistedNoteMetadata to NoteEntity`() { fun `convert PersistedNoteMetadata to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val persistedNoteMetadata = val persistedNoteMetadata =
PersistedNoteMetadata("title", emptyList(), LocalDateTime.MIN, UUID.randomUUID()) PersistedNoteMetadata("title", emptyList(), LocalDateTime.MIN, UUID.randomUUID())
val entity = converter.toEntity(persistedNoteMetadata) val entity = converter.toEntity(persistedNoteMetadata)
@ -116,7 +114,6 @@ internal class NoteConverterTest {
@Test @Test
fun `convert NoteMetadata to NoteEntity`() { fun `convert NoteMetadata to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val noteMetadata = NoteMetadata("title", emptyList()) val noteMetadata = NoteMetadata("title", emptyList())
val entity = converter.toEntity(noteMetadata) val entity = converter.toEntity(noteMetadata)
@ -126,7 +123,6 @@ internal class NoteConverterTest {
@Test @Test
fun `convert PersistedNote to NoteEntity`() { fun `convert PersistedNote to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val persistedNote = PersistedNote( val persistedNote = PersistedNote(
NoteMetadata("title", emptyList()), NoteMetadata("title", emptyList()),
markdown = "md", markdown = "md",
@ -148,7 +144,6 @@ internal class NoteConverterTest {
@Test @Test
fun `convert ExportedNote to NoteEntity`() { fun `convert ExportedNote to NoteEntity`() {
val converter = Mappers.getMapper(NoteConverter::class.java)
val exportedNote = ExportedNote( val exportedNote = ExportedNote(
"title", "title",
emptyList(), emptyList(),

View File

@ -3,15 +3,17 @@ package be.simplenotes.persistance.converters
import be.simplenotes.persistance.users.UserEntity import be.simplenotes.persistance.users.UserEntity
import be.simplenotes.types.PersistedUser import be.simplenotes.types.PersistedUser
import be.simplenotes.types.User import be.simplenotes.types.User
import io.micronaut.context.BeanContext
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mapstruct.factory.Mappers
internal class UserConverterTest { internal class UserConverterTest {
private val ctx = BeanContext.run()
private val converter = ctx.getBean(UserConverter::class.java)
@Test @Test
fun `convert UserEntity to User`() { fun `convert UserEntity to User`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val entity = UserEntity { val entity = UserEntity {
username = "test" username = "test"
password = "test2" password = "test2"
@ -24,7 +26,6 @@ internal class UserConverterTest {
@Test @Test
fun `convert UserEntity to PersistedUser`() { fun `convert UserEntity to PersistedUser`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val entity = UserEntity { val entity = UserEntity {
username = "test" username = "test"
password = "test2" password = "test2"
@ -37,7 +38,6 @@ internal class UserConverterTest {
@Test @Test
fun `convert User to UserEntity`() { fun `convert User to UserEntity`() {
val converter = Mappers.getMapper(UserConverter::class.java)
val user = User("test", "test2") val user = User("test", "test2")
val entity = converter.toEntity(user) val entity = converter.toEntity(user)

View File

@ -16,7 +16,6 @@ import me.liuwj.ktorm.entity.toList
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.* import org.junit.jupiter.api.*
import org.mapstruct.factory.Mappers
import java.sql.SQLIntegrityConstraintViolationException import java.sql.SQLIntegrityConstraintViolationException
internal abstract class BaseNoteRepositoryImplTest : DbTest() { internal abstract class BaseNoteRepositoryImplTest : DbTest() {
@ -142,7 +141,7 @@ internal abstract class BaseNoteRepositoryImplTest : DbTest() {
fun `find an existing note`() { fun `find an existing note`() {
val fakeNote = noteRepo.insertFakeNote(user1) val fakeNote = noteRepo.insertFakeNote(user1)
val converter = Mappers.getMapper(NoteConverter::class.java) val converter = beanContext.getBean(NoteConverter::class.java)
val note = db.notes.find { it.title eq fakeNote.meta.title }!! val note = db.notes.find { it.title eq fakeNote.meta.title }!!
.let { entity -> .let { entity ->