From 21c79beef1ab58d565ec08a7d1b5bf92d0363421 Mon Sep 17 00:00:00 2001 From: Sujan Poudel Date: Tue, 3 Oct 2023 11:56:54 +0545 Subject: [PATCH 1/3] remove support for any serializable object on key value store (#28) --- .../domain/entities/KeyValueEntity.kt | 6 -- .../playdeals/domain/entities/Mappers.kt | 12 +--- .../playdeals/jobs/RedditPostsScrapper.kt | 3 +- .../repositories/KeyValuesRepository.kt | 13 +--- .../PersistentKeyValuesRepository.kt | 19 +++--- .../PersistentKeyValueRepositoryTest.kt | 61 +++++++++++++++++++ 6 files changed, 74 insertions(+), 40 deletions(-) delete mode 100644 backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/KeyValueEntity.kt create mode 100644 backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentKeyValueRepositoryTest.kt diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/KeyValueEntity.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/KeyValueEntity.kt deleted file mode 100644 index e8e4cb5..0000000 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/KeyValueEntity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package me.sujanpoudel.playdeals.domain.entities - -data class KeyValueEntity(val key: String, val value: T) - -val KeyValueEntity<*>.insertValues - get() = arrayOf(key, value) diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/Mappers.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/Mappers.kt index 376e2c6..8e1b44b 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/Mappers.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/domain/entities/Mappers.kt @@ -1,10 +1,8 @@ package me.sujanpoudel.playdeals.domain.entities -import io.vertx.core.json.Json import io.vertx.sqlclient.Row import me.sujanpoudel.playdeals.common.asEnum import me.sujanpoudel.playdeals.common.get -import kotlin.reflect.KClass fun Row.asAppDeal(): DealEntity { return DealEntity( @@ -28,11 +26,5 @@ fun Row.asAppDeal(): DealEntity { ) } -fun Row.asKeyValue(clazz: KClass): KeyValueEntity { - return KeyValueEntity( - get("key"), - getString("value").let { - Json.CODEC.fromValue(it, clazz.java) - } - ) -} +fun Row?.valueOrNull() = this?.getString("value") +fun Row.value(): String = getString("value") diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/RedditPostsScrapper.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/RedditPostsScrapper.kt index 7a8c85d..f3df27b 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/RedditPostsScrapper.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/jobs/RedditPostsScrapper.kt @@ -9,7 +9,6 @@ import me.sujanpoudel.playdeals.common.SIMPLE_NAME import me.sujanpoudel.playdeals.common.loggingExecutionTime import me.sujanpoudel.playdeals.log import me.sujanpoudel.playdeals.repositories.KeyValuesRepository -import me.sujanpoudel.playdeals.repositories.get import org.jobrunr.jobs.lambdas.JobRequest import org.jobrunr.scheduling.JobRequestScheduler import org.jobrunr.scheduling.RecurringJobBuilder @@ -45,7 +44,7 @@ class RedditPostsScrapper( override suspend fun handleRequest(jobRequest: Request): Unit = loggingExecutionTime( "$SIMPLE_NAME:: handleRequest" ) { - val lastPostTime = keyValueRepository.get(LAST_REDDIT_POST_TIME)?.let(OffsetDateTime::parse) + val lastPostTime = keyValueRepository.get(LAST_REDDIT_POST_TIME)?.let(OffsetDateTime::parse) val posts = loggingExecutionTime( "$SIMPLE_NAME:: Fetched reddit post, last created post was at : '$lastPostTime'" diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/KeyValuesRepository.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/KeyValuesRepository.kt index 84570ce..9e02f25 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/KeyValuesRepository.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/KeyValuesRepository.kt @@ -1,18 +1,9 @@ package me.sujanpoudel.playdeals.repositories -import me.sujanpoudel.playdeals.domain.entities.KeyValueEntity -import kotlin.reflect.KClass - interface KeyValuesRepository { - suspend fun set(key: String, value: T, clazz: KClass = value::class): KeyValueEntity + suspend fun set(key: String, value: String): String - suspend fun get(key: String, clazz: KClass): T? + suspend fun get(key: String): String? suspend fun delete(key: String) } - -suspend inline fun KeyValuesRepository.set(key: String, value: T): KeyValueEntity { - return set(key, value, T::class) -} - -suspend inline fun KeyValuesRepository.get(key: String) = get(key, T::class) diff --git a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentKeyValuesRepository.kt b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentKeyValuesRepository.kt index 4c88706..2cd7aaa 100644 --- a/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentKeyValuesRepository.kt +++ b/backend/src/main/kotlin/me/sujanpoudel/playdeals/repositories/persistent/PersistentKeyValuesRepository.kt @@ -1,41 +1,38 @@ package me.sujanpoudel.playdeals.repositories.persistent -import io.vertx.core.json.Json import io.vertx.kotlin.coroutines.await import io.vertx.sqlclient.SqlClient import me.sujanpoudel.playdeals.common.exec -import me.sujanpoudel.playdeals.domain.entities.KeyValueEntity -import me.sujanpoudel.playdeals.domain.entities.asKeyValue +import me.sujanpoudel.playdeals.domain.entities.value +import me.sujanpoudel.playdeals.domain.entities.valueOrNull import me.sujanpoudel.playdeals.repositories.KeyValuesRepository -import kotlin.reflect.KClass - -private fun Any.serializeToString(): String = Json.CODEC.toString(this) class PersistentKeyValuesRepository( private val sqlClient: SqlClient ) : KeyValuesRepository { - override suspend fun set(key: String, value: T, clazz: KClass): KeyValueEntity { + override suspend fun set(key: String, value: String): String { return sqlClient.preparedQuery( """ INSERT INTO "key_value_store" VALUES ($1,$2) ON CONFLICT(key) DO UPDATE SET value = $2 RETURNING * """.trimIndent() - ).exec(key, value.serializeToString()) + ).exec(key, value) .await() .first() - .asKeyValue(clazz) + .value() } - override suspend fun get(key: String, clazz: KClass): T? { + override suspend fun get(key: String): String? { return sqlClient.preparedQuery( """ SELECT * FROM "key_value_store" WHERE key = $1 """.trimIndent() ).exec(key) .await() - .firstOrNull()?.asKeyValue(clazz)?.value + .firstOrNull() + .valueOrNull() } override suspend fun delete(key: String) { diff --git a/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentKeyValueRepositoryTest.kt b/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentKeyValueRepositoryTest.kt new file mode 100644 index 0000000..74f1802 --- /dev/null +++ b/backend/src/test/kotlin/me/sujanpoudel/playdeals/repositories/PersistentKeyValueRepositoryTest.kt @@ -0,0 +1,61 @@ +package me.sujanpoudel.playdeals.repositories + +import io.kotest.matchers.shouldBe +import io.vertx.core.Vertx +import io.vertx.kotlin.coroutines.await +import io.vertx.sqlclient.SqlClient +import me.sujanpoudel.playdeals.IntegrationTest +import me.sujanpoudel.playdeals.common.exec +import me.sujanpoudel.playdeals.domain.entities.value +import me.sujanpoudel.playdeals.get +import org.junit.jupiter.api.Test +import java.time.OffsetDateTime + +class PersistentKeyValueRepositoryTest(vertx: Vertx) : IntegrationTest(vertx) { + + private val repository by lazy { di.get() } + private val sqlClient by lazy { di.get() } + + @Test + fun `should create new entry on db`() = runTest { + val value = repository.set(KEY, "test") + + val valueFromDb = sqlClient.preparedQuery(""" SELECT * from "key_value_store" where key=$1""") + .exec(KEY) + .await() + .first() + .getString("value") + + value shouldBe valueFromDb + } + + @Test + fun `should perform update if item with id already exists`() = runTest { + repository.set(KEY, "test") + + val updated = repository.set(KEY, "test1") + + val fromDb = sqlClient.preparedQuery(""" SELECT * from "key_value_store" where key=$1""") + .exec(KEY) + .await() + .first() + .value() + + fromDb shouldBe updated + } + + @Test + fun `should be able to serialize unknown types`() = runTest { + val value = OffsetDateTime.now() + + repository.set(KEY, value.toString()) + + val fromDb = repository.get(KEY).let(OffsetDateTime::parse) + + fromDb shouldBe value + } + + companion object { + const val KEY = "test_key" + } +} From 060c81be7d8a2a2dede81067dde27cd84ed827b8 Mon Sep 17 00:00:00 2001 From: Sujan Poudel Date: Tue, 3 Oct 2023 12:22:41 +0545 Subject: [PATCH 2/3] add CODEOWNERS (#30) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..3463b6f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @psuzn From 044d5621b7cdd44d2e5b40a3fcebd10092127e44 Mon Sep 17 00:00:00 2001 From: Sujan Poudel Date: Tue, 3 Oct 2023 12:28:30 +0545 Subject: [PATCH 3/3] seperate CI/CD piplelines (#29) --- .github/workflows/cd.yaml | 70 +++++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yaml | 48 ++++----------------------- 2 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/cd.yaml diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 0000000..f247f78 --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,70 @@ +name: play-deals-backend 1.0 CD +on: + push: + tags: + - '*' +jobs: + build_image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@ccb4328a959376b642e027874838f60f8e596de3 + + - name: Build Project with Gradle + uses: gradle/gradle-build-action@749f47bda3e44aa060e82d7b3ef7e40d953bd629 + with: + arguments: build + + - uses: extractions/setup-just@v1 + + - name: Build and publish image with jib + if: ${{ github.ref == 'refs/heads/main' }} + run: just build-push-image + env: + DOCKER_USER: ${{ github.actor }} + DOCKER_PASSWORD: ${{ github.token }} + +deploy_on_k8: + runs-on: ubuntu-latest + needs: build_image + steps: + - uses: actions/checkout@v3 + + - uses: extractions/setup-just@v1 + + - uses: azure/setup-helm@v3 + + - uses: azure/setup-kubectl@v3 + id: install + name: Setup kubectl client + + - name: create KUBECONFIG + if: ${{ github.ref == 'refs/heads/main' }} + run: | + echo ${{ secrets.KUBE_CONFIG }} | base64 --decode > kube-config; + chmod 600 kube-config; + echo "KUBECONFIG=$(pwd)/kube-config" >> "$GITHUB_ENV" + + - name: Deploy on k8 + if: ${{ github.ref == 'refs/heads/main' }} + run: | + kubectl config current-context; + just helm-upgrade; + env: + DB_HOST: ${{ secrets.DB_HOST }} + DB_USERNAME: ${{ secrets.DB_USERNAME }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_PORT: ${{ secrets.DB_PORT }} + DB_NAME: ${{ secrets.DB_NAME }} + DASHBOARD: ${{ secrets.DASHBOARD }} + DASHBOARD_USER: ${{ secrets.DASHBOARD_USER }} + DASHBOARD_PASS: ${{ secrets.DASHBOARD_PASS }} + + diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3598933..077cba2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,64 +1,30 @@ -name: play-deals-backend 1.0 CI - +name: play-deals-backend 1.1 CI on: - push: + pull_request: + workflow_dispatch: jobs: - build: + checks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' + cache: gradle - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@ccb4328a959376b642e027874838f60f8e596de3 + uses: gradle/wrapper-validation-action@v1.1.0 - name: Build Project with Gradle uses: gradle/gradle-build-action@749f47bda3e44aa060e82d7b3ef7e40d953bd629 with: arguments: build - - uses: extractions/setup-just@v1 - - - name: Build and publish image with jib - if: ${{ github.ref == 'refs/heads/main' }} - run: just build-push-image - env: - DOCKER_USER: ${{ github.actor }} - DOCKER_PASSWORD: ${{ github.token }} - - - uses: azure/setup-helm@v3 - - uses: azure/setup-kubectl@v3 - id: install - name: Setup kubectl client - - - name: create KUBECONFIG - if: ${{ github.ref == 'refs/heads/main' }} - run: | - echo ${{ secrets.KUBE_CONFIG }} | base64 --decode > kube-config; - chmod 600 kube-config; - echo "KUBECONFIG=$(pwd)/kube-config" >> "$GITHUB_ENV" - - - name: Deploy on k8 - if: ${{ github.ref == 'refs/heads/main' }} - run: | - kubectl config current-context; - just helm-upgrade; - env: - DB_HOST: ${{ secrets.DB_HOST }} - DB_USERNAME: ${{ secrets.DB_USERNAME }} - DB_PASSWORD: ${{ secrets.DB_PASSWORD }} - DB_PORT: ${{ secrets.DB_PORT }} - DB_NAME: ${{ secrets.DB_NAME }} - DASHBOARD: ${{ secrets.DASHBOARD }} - DASHBOARD_USER: ${{ secrets.DASHBOARD_USER }} - DASHBOARD_PASS: ${{ secrets.DASHBOARD_PASS }} -