Skip to content

Commit

Permalink
Merge pull request #48 from psuzn/develop
Browse files Browse the repository at this point in the history
develop -> main
  • Loading branch information
psuzn authored Jan 3, 2024
2 parents 42e1ffc + 08cdb08 commit 08b8b09
Show file tree
Hide file tree
Showing 16 changed files with 325 additions and 102 deletions.
1 change: 1 addition & 0 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ jobs:
DB_NAME: ${{ secrets.DB_NAME }}
DASHBOARD: ${{ secrets.DASHBOARD }}
FIREBASE_ADMIN_AUTH_CREDENTIALS: ${{ secrets.FIREBASE_ADMIN_AUTH_CREDENTIALS }}
FOREX_API_KEY: ${{ secrets.FOREX_API_KEY }}
DASHBOARD_USER: ${{ secrets.DASHBOARD_USER }}
DASHBOARD_PASS: ${{ secrets.DASHBOARD_PASS }}

Expand Down
49 changes: 28 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Play Deals
[![Static Badge](https://img.shields.io/badge/Android-black?logo=android&logoColor=white&color=%234889f5)](https://play.google.com/store/apps/details?id=me.sujanpoudel.playdeals)  
[![Static Badge](https://img.shields.io/badge/IOS-grey?logo=apple)](https://github.com/psuzn/app-deals/releases/latest)   
[![Static Badge](https://img.shields.io/badge/macOS-black?logo=apple)](https://github.com/psuzn/app-deals/releases/latest)  
[![Static Badge](https://img.shields.io/badge/Windows-green?logo=windows&color=blue)](https://github.com/psuzn/app-deals/releases/latest)  
[![Static Badge](https://img.shields.io/badge/Linux-white?logo=linux&logoColor=white&color=grey)](https://github.com/psuzn/app-deals/releases/latest)  

[![Static Badge](https://img.shields.io/badge/Android-black?logo=android&logoColor=white&color=%234889f5)](https://play.google.com/store/apps/details?id=me.sujanpoudel.playdeals)
 
[![Static Badge](https://img.shields.io/badge/IOS-grey?logo=apple)](https://github.com/psuzn/app-deals/releases/latest)
  
[![Static Badge](https://img.shields.io/badge/macOS-black?logo=apple)](https://github.com/psuzn/app-deals/releases/latest)
 
[![Static Badge](https://img.shields.io/badge/Windows-green?logo=windows&color=blue)](https://github.com/psuzn/app-deals/releases/latest)
 
[![Static Badge](https://img.shields.io/badge/Linux-white?logo=linux&logoColor=white&color=grey)](https://github.com/psuzn/app-deals/releases/latest)
 

![Static Badge](https://img.shields.io/badge/License-GPL--v3-brightgreen)
[![play-deals-backend 1.0 CI](https://github.com/psuzn/play-deals-backend/actions/workflows/ci.yaml/badge.svg)](https://github.com/psuzn/play-deals-backend/actions/workflows/ci.yaml)

![Feature](./media/feature-graphic.jpeg)


| <a href="https://play.google.com/store/apps/details?id=me.sujanpoudel.playdeals"> <img src="media/badge-get-on-google-play.png" width="200" alt="Get it on Google play"> </a> | <a href="https://github.com/psuzn/app-deals/releases/latest"> <img src="media/badge-download-apk.png" width="160" alt="Download Apk"> </a> |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------:|

Expand Down Expand Up @@ -41,21 +46,23 @@ Configuration can be done by passing environment variables listed below:
> 1. run `just dev-run` from terminal, OR
> 2. Install [Envfile](https://plugins.jetbrains.com/plugin/7861-envfile) plugin for IntelliJ and run using IntelliJ
| ENV_VAR | REQUIRED | DEFAULT | EXAMPLE | NOTES |
|------------------|----------|---------------|--------------|:--------------------------------------------------------------------|
| `DB_HOST` | `Y` | | `localhost` | |
| `DB_USERNAME` | `Y` | | `whatever` | |
| `DB_PASSWORD` | `N` | `password` | `whatever` | |
| `DB_PORT` | `N` | `5432` | `6868` | |
| `DB_NAME` | `N` | `play_deals` | `whatever` | |
| `DB_POOL_SIZE` | `N` | `5` | `6` | |
| `ENV` | `N` | `PRODUCTION` | `PRODUCTION` | one of `PRODUCTION or DEVELOPMENT or TEST ` |
| `APP_PORT` | `N` | `8888` | `9999` | |
| `POSTGRES_IMAGE` | `N` | `postgres:14` | | Useful for testing new versions of postgres. Used only in test code |
| `DASHBOARD` | `N` | `true` | `false` | Whether to enable or not the Jobrunr dashboard |
| `DASHBOARD_USER` | `N` | `admin` | `whatever` | Jobrunr dashboard login credential |
| `DASHBOARD_PASS` | `N` | `admin` | `whatever` | Jobrunr dashboard login credential |
| `CORS` | `N` | `*` | `whatever` | origins allowed for CORS |
| ENV_VAR | REQUIRED | DEFAULT | EXAMPLE | NOTES |
|-----------------------------------|----------|---------------|--------------|:-----------------------------------------------------------------------|
| `DB_HOST` | `Y` | | `localhost` | |
| `DB_USERNAME` | `Y` | | `whatever` | |
| `FIREBASE_ADMIN_AUTH_CREDENTIALS` | `Y` | | `whatever` | Firebase admin auth credentials |
| `FOREX_API_KEY` | `Y` | | `whatever` | Api key for [https://exchangeratesapi.io](https://exchangeratesapi.io) |
| `DB_PASSWORD` | `N` | `password` | `whatever` | |
| `DB_PORT` | `N` | `5432` | `6868` | |
| `DB_NAME` | `N` | `play_deals` | `whatever` | |
| `DB_POOL_SIZE` | `N` | `5` | `6` | |
| `ENV` | `N` | `PRODUCTION` | `PRODUCTION` | one of `PRODUCTION or DEVELOPMENT or TEST ` |
| `APP_PORT` | `N` | `8888` | `9999` | |
| `POSTGRES_IMAGE` | `N` | `postgres:14` | | Useful for testing new versions of postgres. Used only in test code |
| `DASHBOARD` | `N` | `true` | `false` | Whether to enable or not the Jobrunr dashboard |
| `DASHBOARD_USER` | `N` | `admin` | `whatever` | Jobrunr dashboard login credential |
| `DASHBOARD_PASS` | `N` | `admin` | `whatever` | Jobrunr dashboard login credential |
| `CORS` | `N` | `*` | `whatever` | origins allowed for CORS |

## License

Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import me.sujanpoudel.playdeals.jobs.AndroidAppExpiryCheckScheduler
import me.sujanpoudel.playdeals.jobs.AppDetailScrapper
import me.sujanpoudel.playdeals.jobs.BackgroundJobsVerticle
import me.sujanpoudel.playdeals.jobs.DealSummarizer
import me.sujanpoudel.playdeals.jobs.ForexFetcher
import me.sujanpoudel.playdeals.jobs.RedditPostsScrapper
import me.sujanpoudel.playdeals.repositories.DealRepository
import me.sujanpoudel.playdeals.repositories.KeyValuesRepository
Expand All @@ -28,6 +29,7 @@ import me.sujanpoudel.playdeals.repositories.persistent.PersistentKeyValuesRepos
import me.sujanpoudel.playdeals.services.MessagingService
import me.sujanpoudel.playdeals.usecases.DBHealthUseCase
import me.sujanpoudel.playdeals.usecases.GetDealsUseCase
import me.sujanpoudel.playdeals.usecases.GetForexUseCase
import me.sujanpoudel.playdeals.usecases.NewDealUseCase
import org.flywaydb.core.Flyway
import org.jobrunr.configuration.JobRunr
Expand Down Expand Up @@ -155,10 +157,17 @@ fun configureDI(
requestScheduler = instance()
)
}
bindSingleton {
ForexFetcher(
di = di,
conf = instance()
)
}

bindSingleton { DBHealthUseCase(di) }
bindSingleton { GetDealsUseCase(di) }
bindSingleton { NewDealUseCase(di) }
bindSingleton { GetForexUseCase(di) }

bindSingleton<FirebaseOptions> {
FirebaseOptions.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ApiVerticle(
router.route().handler(CorsHandler.create().addRelativeOrigin(config.api.cors))
router.route("/health/*").subRouter(healthApi(di, vertx))
router.route("/api/deals/*").subRouter(appDealsApi(di, vertx))
router.route("/api/forex/*").subRouter(forexRateApi(di, vertx))

vertx.createHttpServer()
.requestHandler(router)
Expand Down
25 changes: 25 additions & 0 deletions backend/src/main/kotlin/me/sujanpoudel/playdeals/api/ForexRate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package me.sujanpoudel.playdeals.api

import io.vertx.ext.web.Router
import me.sujanpoudel.playdeals.common.coHandler
import me.sujanpoudel.playdeals.common.jsonResponse
import me.sujanpoudel.playdeals.usecases.GetForexUseCase
import me.sujanpoudel.playdeals.usecases.executeUseCase
import org.kodein.di.DirectDI
import org.kodein.di.instance

fun forexRateApi(
di: DirectDI,
vertx: io.vertx.core.Vertx
): Router = Router.router(vertx).apply {
get()
.coHandler { ctx ->
ctx.executeUseCase(
useCase = di.instance<GetForexUseCase>(),
toContext = { },
toInput = { }
) {
ctx.json(jsonResponse(data = it))
}
}
}
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
@@ -0,0 +1,11 @@
package me.sujanpoudel.playdeals.domain

import java.time.OffsetDateTime

// Rates are USD based
data class ForexRate(
val timestamp: OffsetDateTime,
val rates: List<ConversionRate>
)

data class ConversionRate(val currency: String, val rate: Float)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.sujanpoudel.playdeals.jobs

import io.vertx.kotlin.coroutines.CoroutineVerticle
import me.sujanpoudel.playdeals.repositories.KeyValuesRepository
import org.jobrunr.configuration.JobRunr
import org.jobrunr.configuration.JobRunrConfiguration
import org.jobrunr.scheduling.JobRequestScheduler
Expand All @@ -14,16 +15,22 @@ class BackgroundJobsVerticle(
) : CoroutineVerticle(), DIAware {

private val jobRequestScheduler by instance<JobRequestScheduler>()
private val keyValuesRepository by instance<KeyValuesRepository>()

override suspend fun start() {
direct.instance<JobRunrConfiguration.JobRunrConfigurationResult>()
setupRecurringJobs()
}

private fun setupRecurringJobs() {
private suspend fun setupRecurringJobs() {
jobRequestScheduler.createRecurrently(RedditPostsScrapper.Request())
jobRequestScheduler.createRecurrently(AndroidAppExpiryCheckScheduler.Request())
jobRequestScheduler.createRecurrently(DealSummarizer.Request())
jobRequestScheduler.createRecurrently(ForexFetcher.Request())

if (keyValuesRepository.getForexRate() == null) {
jobRequestScheduler.enqueue(ForexFetcher.Request.immediate())
}
}

override suspend fun stop() {
Expand Down
Loading

0 comments on commit 08b8b09

Please sign in to comment.