Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

forex api key + refactor #45

Merged
merged 1 commit into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/src/main/kotlin/me/sujanpoudel/playdeals/Conf.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
87 changes: 41 additions & 46 deletions backend/src/main/kotlin/me/sujanpoudel/playdeals/common/Conf.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("UNCHECKED_CAST")

package me.sujanpoudel.playdeals.common

import me.sujanpoudel.playdeals.Conf
Expand All @@ -7,74 +9,67 @@ import java.util.Base64

class BootstrapException(val violations: List<String>) : RuntimeException()

fun buildConf(env: Map<String, String>) = com.github.michaelbull.result.runCatching {
fun buildConf(envs: Map<String, String>) = com.github.michaelbull.result.runCatching {
val violations = mutableListOf<String>()

val environment = env.getOrDefault("ENV", Environment.PRODUCTION.name).asEnumOrNull<Environment>()

if (environment == null) {
violations += "Invalid ENV"
}

val dashboardEnabled = env.getOrDefault("DASHBOARD", "true").toBooleanStrictOrNull()
if (dashboardEnabled == null) {
violations += "Invalid DASHBOARD"
@Suppress("UNCHECKED_CAST")
fun <T> 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<Environment>() }

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<String>("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<String>("DB_NAME", "play_deals")
val dbPoolSize = env("DB_POOL_SIZE", "5") { it.toIntOrNull() }
val dbHost = env<String>("DB_HOST")
val dbUsername = env<String>("DB_USERNAME")
val dbPassword = env<String>("DB_PASSWORD", "password")

fun envVar(envVarName: String): String? {
val value = env[envVarName]
val dashboardEnabled = env("DASHBOARD", "true") { it.toBooleanStrictOrNull() }
val dashboardUser = env<String>("DASHBOARD_USER", "admin")
val dashboardPassword = env<String>("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<String>("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!!
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ abstract class IntegrationTest(private val vertx: Vertx) {
"admin",
"admin"
),
firebaseAuthCredential = ""
firebaseAuthCredential = "",
forexApiKey = ""
)

var di = configureDI(vertx, conf)
Expand Down
68 changes: 36 additions & 32 deletions backend/src/test/kotlin/me/sujanpoudel/playdeals/MainTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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'"
)
}
}