From dfe5c3acce3f1d48f3cf56de3a1ab90675cc31a3 Mon Sep 17 00:00:00 2001 From: Sujan Poudel Date: Tue, 2 Jan 2024 20:28:22 +0545 Subject: [PATCH] forex api config + refactor (#45) --- .../kotlin/me/sujanpoudel/playdeals/Conf.kt | 3 +- .../me/sujanpoudel/playdeals/common/Conf.kt | 87 +++++++++---------- .../sujanpoudel/playdeals/IntegrationTest.kt | 3 +- .../me/sujanpoudel/playdeals/MainTest.kt | 68 ++++++++------- 4 files changed, 81 insertions(+), 80 deletions(-) diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/Conf.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/Conf.kt index 6fe7044..b745318 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/Conf.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/Conf.kt @@ -11,7 +11,8 @@ data class Conf( val api: Api, val environment: Environment, val backgroundTask: BackgroundTask, - val firebaseAuthCredential: String + val firebaseAuthCredential: String, + val forexApiKey: String ) { data class DB( val host: String, diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt index 291d589..d0279ab 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt @@ -1,3 +1,5 @@ +@file:Suppress("UNCHECKED_CAST") + package me.sujanpoudel.playdeals.common import me.sujanpoudel.playdeals.Conf @@ -7,74 +9,67 @@ import java.util.Base64 class BootstrapException(val violations: List) : RuntimeException() -fun buildConf(env: Map) = com.github.michaelbull.result.runCatching { +fun buildConf(envs: Map) = com.github.michaelbull.result.runCatching { val violations = mutableListOf() - val environment = env.getOrDefault("ENV", Environment.PRODUCTION.name).asEnumOrNull() - - if (environment == null) { - violations += "Invalid ENV" - } - - val dashboardEnabled = env.getOrDefault("DASHBOARD", "true").toBooleanStrictOrNull() - if (dashboardEnabled == null) { - violations += "Invalid DASHBOARD" + @Suppress("UNCHECKED_CAST") + fun env( + envVarName: String, + default: String? = null, + converter: (String) -> T? = { it as? T } + ): T? = ( + envs[envVarName] ?: default ?: run { + violations += "No '$envVarName' env var defined!".also { logger.error { it } } + null + } + )?.let(converter) ?: run { + violations += "Invalid '$envVarName'" + null } - val appPort = env.getOrDefault("APP_PORT", "8888").toIntOrNull() - - if (appPort == null) { - violations += "Invalid APP_PORT" - } + val environment = env("ENV", Environment.PRODUCTION.name) { it.asEnumOrNull() } - val dbPort = env.getOrDefault("DB_PORT", "5432").toIntOrNull() - if (dbPort == null) { - violations += "Invalid DB_PORT" - } + val appPort = env("APP_PORT", "8888") { it.toIntOrNull() } + val cors = env("CORS", ".*.") - val dbName = env.getOrDefault("DB_NAME", "play_deals") - val dbPoolSize = (env["DB_POOL_SIZE"] ?: "5").toIntOrNull() - if (dbPoolSize == null) { - violations += "Invalid DB_POOL_SIZE" - } + val dbPort = env("DB_PORT", "5432") { it.toIntOrNull() } + val dbName = env("DB_NAME", "play_deals") + val dbPoolSize = env("DB_POOL_SIZE", "5") { it.toIntOrNull() } + val dbHost = env("DB_HOST") + val dbUsername = env("DB_USERNAME") + val dbPassword = env("DB_PASSWORD", "password") - fun envVar(envVarName: String): String? { - val value = env[envVarName] + val dashboardEnabled = env("DASHBOARD", "true") { it.toBooleanStrictOrNull() } + val dashboardUser = env("DASHBOARD_USER", "admin") + val dashboardPassword = env("DASHBOARD_PASS", "admin") - return if (value.isNullOrBlank()) { - violations += "No $envVarName env var defined!".also { logger.error { it } } - null - } else { - value - } + val firebaseAuthCredential = env("FIREBASE_ADMIN_AUTH_CREDENTIALS") { + Base64.getDecoder().decode(it).decodeToString() } - val dbHost: String = envVar("DB_HOST").orEmpty() - val dbUsername: String = envVar("DB_USERNAME").orEmpty() - val firebaseAuthCredential = envVar("FIREBASE_ADMIN_AUTH_CREDENTIALS")?.let { - Base64.getDecoder().decode(it).decodeToString() - }.orEmpty() + val forexApiKey = env("FOREX_API_KEY") if (violations.isNotEmpty()) { throw BootstrapException(violations) } else { Conf( - api = Conf.Api(appPort!!, cors = env.getOrDefault("CORS", ".*.")), + api = Conf.Api(appPort!!, cors = cors!!), environment = environment!!, db = Conf.DB( - host = dbHost, + host = dbHost!!, port = dbPort!!, - name = dbName, - username = dbUsername, - password = env.getOrDefault("DB_PASSWORD", "password"), + name = dbName!!, + username = dbUsername!!, + password = dbPassword!!, poolSize = dbPoolSize!! ), backgroundTask = Conf.BackgroundTask( - dashboardEnabled!!, - env.getOrDefault("DASHBOARD_USER", "admin"), - env.getOrDefault("DASHBOARD_PASS", "admin") + dashboardEnabled = dashboardEnabled!!, + dashboardUserName = dashboardUser!!, + dashboardPassword = dashboardPassword!! ), - firebaseAuthCredential = firebaseAuthCredential + firebaseAuthCredential = firebaseAuthCredential!!, + forexApiKey = forexApiKey!! ) } } diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/IntegrationTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/IntegrationTest.kt index 1f6f1c0..adad98c 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/IntegrationTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/IntegrationTest.kt @@ -56,7 +56,8 @@ abstract class IntegrationTest(private val vertx: Vertx) { "admin", "admin" ), - firebaseAuthCredential = "" + firebaseAuthCredential = "", + forexApiKey = "" ) var di = configureDI(vertx, conf) diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/MainTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/MainTest.kt index 3b374b8..0c8e1ee 100644 --- a/backend/src/test/kotlin/me/sujanpoudel/playdeals/MainTest.kt +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/MainTest.kt @@ -3,7 +3,6 @@ package me.sujanpoudel.playdeals import com.github.michaelbull.result.unwrap import com.github.michaelbull.result.unwrapError import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.shouldBe import me.sujanpoudel.playdeals.common.BootstrapException @@ -14,6 +13,7 @@ class MainTest { @Test fun `Should return a proper conf with all values from env`() { val env = mutableMapOf( + "ENV" to "DEVELOPMENT", "APP_PORT" to "123", "CORS" to "*.example.com", @@ -28,14 +28,14 @@ class MainTest { "DB_PORT" to "3333", "DB_NAME" to "db-name", - "APP_PORT" to "123", - "FIREBASE_ADMIN_AUTH_CREDENTIALS" to "dGVzdF9jcmVk", - "ENV" to "DEVELOPMENT" + "FOREX_API_KEY" to "forex_key" ) val conf = buildConf(env).unwrap() + conf.environment shouldBe Environment.DEVELOPMENT + conf.api.port shouldBe 123 conf.api.cors shouldBe "*.example.com" conf.backgroundTask.dashboardEnabled shouldBe true @@ -49,41 +49,40 @@ class MainTest { conf.db.username shouldBe "u" conf.db.port shouldBe 3333 - conf.api.port shouldBe 123 - - conf.environment shouldBe Environment.DEVELOPMENT - } - - @Test - fun `Should fail on first critical incorrect val from env`() { - val env = mutableMapOf( - "ENV" to "prod" - ) - - val err = buildConf(env).unwrapError() - err.printStackTrace() - val violations = (err as BootstrapException).violations - violations.shouldHaveSize(4) shouldContainExactlyInAnyOrder listOf( - "Invalid ENV", - "No DB_HOST env var defined!", - "No DB_USERNAME env var defined!", - "No FIREBASE_ADMIN_AUTH_CREDENTIALS env var defined!" - ) + conf.firebaseAuthCredential shouldBe "test_cred" + conf.forexApiKey shouldBe "forex_key" } @Test fun `Should return a proper conf with some defaults being taken`() { val env = mutableMapOf( + "DB_PORT" to "3333", "DB_HOST" to "localhost", "DB_USERNAME" to "u", "DB_PASSWORD" to "p", - "DB_PORT" to "3333", - "DB_NAME" to "db-name", - "FIREBASE_ADMIN_AUTH_CREDENTIALS" to "dGVzdF9jcmVk" + "FIREBASE_ADMIN_AUTH_CREDENTIALS" to "dGVzdF9jcmVk", + "FOREX_API_KEY" to "forex_key" ) val conf = buildConf(env).unwrap() + + conf.environment shouldBe Environment.PRODUCTION conf.api.port shouldBe 8888 + conf.api.cors shouldBe ".*." + + conf.backgroundTask.dashboardEnabled shouldBe true + conf.backgroundTask.dashboardUserName shouldBe "admin" + conf.backgroundTask.dashboardPassword shouldBe "admin" + + conf.db.name shouldBe "play_deals" + conf.db.host shouldBe "localhost" + conf.db.password shouldBe "p" + conf.db.poolSize shouldBe 5 + conf.db.username shouldBe "u" + conf.db.port shouldBe 3333 + + conf.firebaseAuthCredential shouldBe "test_cred" + conf.forexApiKey shouldBe "forex_key" } @Test @@ -96,11 +95,16 @@ class MainTest { val violations = ((buildConf(env).unwrapError()) as BootstrapException).violations violations.shouldNotBeEmpty() violations shouldContainExactlyInAnyOrder listOf( - "Invalid APP_PORT", - "Invalid ENV", - "No DB_HOST env var defined!", - "No DB_USERNAME env var defined!", - "No FIREBASE_ADMIN_AUTH_CREDENTIALS env var defined!" + "Invalid 'ENV'", + "Invalid 'APP_PORT'", + "No 'DB_HOST' env var defined!", + "Invalid 'DB_HOST'", + "No 'DB_USERNAME' env var defined!", + "Invalid 'DB_USERNAME'", + "No 'FIREBASE_ADMIN_AUTH_CREDENTIALS' env var defined!", + "Invalid 'FIREBASE_ADMIN_AUTH_CREDENTIALS'", + "No 'FOREX_API_KEY' env var defined!", + "Invalid 'FOREX_API_KEY'" ) } }