Load configuration with micronaut
This commit is contained in:
parent
4f395d254d
commit
941380ad16
12
.env.dist
12
.env.dist
@ -1,10 +1,6 @@
|
||||
## can be generated with `openssl rand -base64 32`
|
||||
JWT_SECRET=
|
||||
#
|
||||
## can be generated with `openssl rand -base64 32`
|
||||
# mariadb
|
||||
MYSQL_ROOT_PASSWORD=
|
||||
#
|
||||
## can be generated with `openssl rand -base64 32`
|
||||
MYSQL_PASSWORD=
|
||||
# password should be the same as mysql_password
|
||||
PASSWORD=
|
||||
# simplenotes
|
||||
DB_PASSWORD=
|
||||
JWT_SECRET=
|
||||
|
||||
@ -15,5 +15,4 @@
|
||||
## Configuration
|
||||
|
||||
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.
|
||||
Don't use the default values for secrets ! Every value inside *.env.dist* should be changed.
|
||||
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.
|
||||
|
||||
@ -32,13 +32,13 @@ services:
|
||||
- .env
|
||||
environment:
|
||||
- TZ=Europe/Brussels
|
||||
- HOST=0.0.0.0
|
||||
- JDBCURL=jdbc:mariadb://db:3306/simplenotes
|
||||
- DRIVERCLASSNAME=org.mariadb.jdbc.Driver
|
||||
- USERNAME=simplenotes
|
||||
- SERVER_HOST=0.0.0.0
|
||||
- DB_JDBC_URL=jdbc:mariadb://db:3306/simplenotes
|
||||
- DB_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver
|
||||
- DB_USERNAME=simplenotes
|
||||
# .env:
|
||||
# - JWT_SECRET
|
||||
# - PASSWORD
|
||||
# - DB_PASSWORD
|
||||
ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
healthcheck:
|
||||
|
||||
@ -30,9 +30,6 @@ dependencies {
|
||||
implementation(Libs.micronaut)
|
||||
kapt(Libs.micronautProcessor)
|
||||
|
||||
testImplementation(Libs.micronaut)
|
||||
kaptTest(Libs.micronautProcessor)
|
||||
|
||||
testImplementation(Libs.junit)
|
||||
testImplementation(Libs.assertJ)
|
||||
testImplementation(Libs.http4kTestingHamkrest)
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package be.simplenotes.app
|
||||
|
||||
import io.micronaut.context.annotation.Context
|
||||
import org.http4k.server.Http4kServer
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.annotation.PostConstruct
|
||||
import javax.annotation.PreDestroy
|
||||
import javax.inject.Singleton
|
||||
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
|
||||
|
||||
@Context
|
||||
@Singleton
|
||||
class Server(
|
||||
private val config: SimpleNotesServerConfig,
|
||||
private val http4kServer: Http4kServer,
|
||||
@ -17,7 +17,7 @@ class Server(
|
||||
@PostConstruct
|
||||
fun start(): Server {
|
||||
http4kServer.start()
|
||||
logger.info("Listening on http://${config.host}:${config.port}")
|
||||
logger.info("Listening on http://${config.host}:${http4kServer.port()}")
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
package be.simplenotes.app
|
||||
|
||||
import io.micronaut.context.ApplicationContext
|
||||
import java.lang.Runtime.getRuntime
|
||||
|
||||
fun main() {
|
||||
val ctx = ApplicationContext.run().start()
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
Thread {
|
||||
ctx.stop()
|
||||
}
|
||||
)
|
||||
val ctx = ApplicationContext.run()
|
||||
ctx.createBean(Server::class.java)
|
||||
getRuntime().addShutdownHook(Thread { ctx.stop() })
|
||||
}
|
||||
|
||||
@ -14,4 +14,5 @@
|
||||
<logger name="com.zaxxer.hikari" level="INFO"/>
|
||||
<logger name="org.flywaydb.core" level="INFO"/>
|
||||
<logger name="io.micronaut" level="INFO"/>
|
||||
<logger name="io.micronaut.context.lifecycle" level="DEBUG"/>
|
||||
</configuration>
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
package be.simplenotes.config
|
||||
|
||||
import io.micronaut.context.annotation.ConfigurationInject
|
||||
import io.micronaut.context.annotation.ConfigurationProperties
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
data class DataSourceConfig(
|
||||
@ConfigurationProperties("db")
|
||||
data class DataSourceConfig @ConfigurationInject constructor(
|
||||
val jdbcUrl: String,
|
||||
val driverClassName: String,
|
||||
val username: String,
|
||||
@ -14,7 +18,8 @@ data class DataSourceConfig(
|
||||
"username='$username', password='***', maximumPoolSize=$maximumPoolSize, connectionTimeout=$connectionTimeout)"
|
||||
}
|
||||
|
||||
data class JwtConfig(
|
||||
@ConfigurationProperties("jwt")
|
||||
data class JwtConfig @ConfigurationInject constructor(
|
||||
val secret: String,
|
||||
val validity: Long,
|
||||
val timeUnit: TimeUnit,
|
||||
@ -22,7 +27,8 @@ data class JwtConfig(
|
||||
override fun toString() = "JwtConfig(secret='***', validity=$validity, timeUnit=$timeUnit)"
|
||||
}
|
||||
|
||||
data class ServerConfig(
|
||||
@ConfigurationProperties("server")
|
||||
data class ServerConfig @ConfigurationInject constructor(
|
||||
val host: String,
|
||||
val port: Int,
|
||||
)
|
||||
|
||||
@ -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(),
|
||||
)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
16
simplenotes-config/src/main/resources/application.yaml
Normal file
16
simplenotes-config/src/main/resources/application.yaml
Normal 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
|
||||
@ -16,16 +16,14 @@ dependencies {
|
||||
implementation(Libs.hikariCP)
|
||||
implementation(Libs.ktormCore)
|
||||
implementation(Libs.ktormMysql)
|
||||
implementation(Libs.logbackClassic)
|
||||
|
||||
implementation(Libs.mapstruct)
|
||||
compileOnly(Libs.mapstruct)
|
||||
kapt(Libs.mapstructProcessor)
|
||||
|
||||
implementation(Libs.micronaut)
|
||||
kapt(Libs.micronautProcessor)
|
||||
|
||||
testImplementation(Libs.micronaut)
|
||||
kaptTest(Libs.micronautProcessor)
|
||||
|
||||
testImplementation(Libs.junit)
|
||||
testImplementation(Libs.assertJ)
|
||||
testImplementation(Libs.logbackClassic)
|
||||
|
||||
@ -15,12 +15,6 @@ import javax.sql.DataSource
|
||||
@Factory
|
||||
class PersistanceModule {
|
||||
|
||||
@Singleton
|
||||
internal fun noteConverter() = Mappers.getMapper(NoteConverter::class.java)
|
||||
|
||||
@Singleton
|
||||
internal fun userConverter() = Mappers.getMapper(UserConverter::class.java)
|
||||
|
||||
@Singleton
|
||||
internal fun database(migrations: DbMigrations, dataSource: DataSource): Database {
|
||||
migrations.migrate()
|
||||
|
||||
@ -9,12 +9,13 @@ import org.mapstruct.Mappings
|
||||
import org.mapstruct.ReportingPolicy
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* This is an abstract class because kotlin default methods in interface are not seen as default in kapt
|
||||
* @see [KT-25960](https://youtrack.jetbrains.com/issue/KT-25960)
|
||||
*/
|
||||
@Mapper(uses = [NoteEntityFactory::class, UserEntityFactory::class], unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
@Mapper(
|
||||
uses = [NoteEntityFactory::class, UserEntityFactory::class],
|
||||
unmappedTargetPolicy = ReportingPolicy.IGNORE,
|
||||
componentModel = "jsr330"
|
||||
)
|
||||
internal abstract class NoteConverter {
|
||||
|
||||
fun toNote(entity: NoteEntity, tags: Tags) =
|
||||
@ -80,4 +81,5 @@ internal abstract class NoteConverter {
|
||||
|
||||
typealias Tags = List<String>
|
||||
|
||||
@Singleton
|
||||
internal class NoteEntityFactory : Entity.Factory<NoteEntity>()
|
||||
|
||||
@ -6,8 +6,13 @@ import be.simplenotes.types.User
|
||||
import me.liuwj.ktorm.entity.Entity
|
||||
import org.mapstruct.Mapper
|
||||
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 {
|
||||
fun toUser(userEntity: UserEntity): User
|
||||
fun toPersistedUser(userEntity: UserEntity): PersistedUser
|
||||
@ -15,4 +20,5 @@ internal interface UserConverter {
|
||||
fun toEntity(user: PersistedUser): UserEntity
|
||||
}
|
||||
|
||||
@Singleton
|
||||
internal class UserEntityFactory : Entity.Factory<UserEntity>()
|
||||
|
||||
@ -2,23 +2,25 @@ package be.simplenotes.persistance.converters
|
||||
|
||||
import be.simplenotes.persistance.notes.NoteEntity
|
||||
import be.simplenotes.types.*
|
||||
import io.micronaut.context.BeanContext
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mapstruct.factory.Mappers
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
|
||||
internal class NoteConverterTest {
|
||||
|
||||
private val ctx = BeanContext.run()
|
||||
val converter = ctx.getBean(NoteConverter::class.java)
|
||||
|
||||
@Nested
|
||||
@DisplayName("Entity -> Models")
|
||||
inner class EntityToModels {
|
||||
|
||||
@Test
|
||||
fun `convert NoteEntity to Note`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val entity = NoteEntity {
|
||||
title = "title"
|
||||
markdown = "md"
|
||||
@ -39,7 +41,6 @@ internal class NoteConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert NoteEntity to ExportedNote`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val entity = NoteEntity {
|
||||
title = "title"
|
||||
markdown = "md"
|
||||
@ -62,7 +63,6 @@ internal class NoteConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert NoteEntity to PersistedNoteMetadata`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val entity = NoteEntity {
|
||||
uuid = UUID.randomUUID()
|
||||
title = "title"
|
||||
@ -89,7 +89,6 @@ internal class NoteConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert Note to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val note = Note(NoteMetadata("title", emptyList()), "md", "html")
|
||||
val entity = converter.toEntity(note, UUID.randomUUID(), 2, LocalDateTime.MIN)
|
||||
|
||||
@ -103,7 +102,6 @@ internal class NoteConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert PersistedNoteMetadata to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val persistedNoteMetadata =
|
||||
PersistedNoteMetadata("title", emptyList(), LocalDateTime.MIN, UUID.randomUUID())
|
||||
val entity = converter.toEntity(persistedNoteMetadata)
|
||||
@ -116,7 +114,6 @@ internal class NoteConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert NoteMetadata to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val noteMetadata = NoteMetadata("title", emptyList())
|
||||
val entity = converter.toEntity(noteMetadata)
|
||||
|
||||
@ -126,7 +123,6 @@ internal class NoteConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert PersistedNote to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val persistedNote = PersistedNote(
|
||||
NoteMetadata("title", emptyList()),
|
||||
markdown = "md",
|
||||
@ -148,7 +144,6 @@ internal class NoteConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert ExportedNote to NoteEntity`() {
|
||||
val converter = Mappers.getMapper(NoteConverter::class.java)
|
||||
val exportedNote = ExportedNote(
|
||||
"title",
|
||||
emptyList(),
|
||||
|
||||
@ -3,15 +3,17 @@ package be.simplenotes.persistance.converters
|
||||
import be.simplenotes.persistance.users.UserEntity
|
||||
import be.simplenotes.types.PersistedUser
|
||||
import be.simplenotes.types.User
|
||||
import io.micronaut.context.BeanContext
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mapstruct.factory.Mappers
|
||||
|
||||
internal class UserConverterTest {
|
||||
|
||||
private val ctx = BeanContext.run()
|
||||
private val converter = ctx.getBean(UserConverter::class.java)
|
||||
|
||||
@Test
|
||||
fun `convert UserEntity to User`() {
|
||||
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||
val entity = UserEntity {
|
||||
username = "test"
|
||||
password = "test2"
|
||||
@ -24,7 +26,6 @@ internal class UserConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert UserEntity to PersistedUser`() {
|
||||
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||
val entity = UserEntity {
|
||||
username = "test"
|
||||
password = "test2"
|
||||
@ -37,7 +38,6 @@ internal class UserConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convert User to UserEntity`() {
|
||||
val converter = Mappers.getMapper(UserConverter::class.java)
|
||||
val user = User("test", "test2")
|
||||
val entity = converter.toEntity(user)
|
||||
|
||||
|
||||
@ -16,7 +16,6 @@ import me.liuwj.ktorm.entity.toList
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.jupiter.api.*
|
||||
import org.mapstruct.factory.Mappers
|
||||
import java.sql.SQLIntegrityConstraintViolationException
|
||||
|
||||
internal abstract class BaseNoteRepositoryImplTest : DbTest() {
|
||||
@ -142,7 +141,7 @@ internal abstract class BaseNoteRepositoryImplTest : DbTest() {
|
||||
fun `find an existing note`() {
|
||||
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 }!!
|
||||
.let { entity ->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user