diff --git a/build-logic/src/main/kotlin/SecretsPlugin.kt b/build-logic/src/main/kotlin/SecretsPlugin.kt index a11e403..7461d39 100644 --- a/build-logic/src/main/kotlin/SecretsPlugin.kt +++ b/build-logic/src/main/kotlin/SecretsPlugin.kt @@ -1,34 +1,38 @@ import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.provider.Provider import org.gradle.api.tasks.SourceSetContainer import java.io.File import java.util.Properties abstract class SecretsPlugin : Plugin { override fun apply(project: Project) { - fun String.asRootFile() = - project.rootProject.file(this) + fun String.asProps(filterBy: (Map.Entry<*,*>) -> Boolean = { true }) : Properties = + project.rootProject.file(this).asProps().filter(filterBy).asProperties() - val secrets = SECRETS_FILE.asRootFile() - val dir = project.layout.buildDirectory.dir(OUTPUT_DIR) + fun String.asDir() = + project.layout.buildDirectory.dir(this) + + val secrets = SECRETS_PROPERTIES_FILE.asProps() + val local = GRADLE_PROPERTIES_FILE.asProps { + it.key == KEY_USE_LOCAL_MOCKS + } + val dir = OUTPUT_DIR.asDir() val generateSecrets = project.tasks.register(TASK_NAME) { outputs.dir(dir) doLast { + val useLocal = local.isTrue() + val properties = when { + useLocal -> onUseLocalMocks() + !useLocal && !secrets.isValid() -> onUseRemoteButNotSecrets() + else -> listOf(local, secrets) + } - val useLocal = GRADLE_PROPS_FILE.asRootFile().asProps().filter { it.key == KEY_USE_LOCAL_MOCKS } - if(useLocal.isTrue()) - println(USE_LOCAL_MOCKS_MSG) - else if(useLocal.isFalse() && !secrets.exists()) - error(MISSING_SECRET_PROPS_ERROR_MSG) - - dir.get().asFile.resolve("$OUTPUT_FILE.kt").apply { + dir.createFile(OUTPUT_FILE) { parentFile.mkdirs() - val text = listOf(useLocal, secrets.asProps()) - .flatMap { it.entries } - .fold(setOf>()) { acc, it -> acc + setOf(it.toStringPair()) } - .asConstants() - writeText(text) + writeText(properties.asText()) } } } @@ -45,18 +49,21 @@ abstract class SecretsPlugin : Plugin { } companion object { - private const val GRADLE_PROPS_FILE = "gradle.properties" - private const val SECRETS_FILE = "secrets.properties" - private const val KEY_USE_LOCAL_MOCKS = "USE_LOCAL_MOCKS" + private const val GRADLE_PROPERTIES_FILE = "gradle.properties" + private const val SECRETS_PROPERTIES_FILE = "secrets.properties" private const val OUTPUT_DIR = "generated/secrets" private const val OUTPUT_FILE = "Secrets" private const val TASK_NAME = "generateSecrets" - private val USE_LOCAL_MOCKS_MSG = """ + private const val KEY_USE_LOCAL_MOCKS = "USE_LOCAL_MOCKS" + private const val KEY_SERVER_URL = "SERVER_URL" + private const val KEY_CLIENT_KEY = "CLIENT_KEY" + + private val useLocalMocksMsg = """ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ℹ️ INFO ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - This project's '${SECRETS_FILE}' either does not contain the key '$KEY_USE_LOCAL_MOCKS' or its value is + This project's '${GRADLE_PROPERTIES_FILE}' either does not contain the key '$KEY_USE_LOCAL_MOCKS' or its value is set to true. To use a local instance of Supabase-CLI, set this property to false and follow the instructions on the Roky @@ -65,34 +72,69 @@ abstract class SecretsPlugin : Plugin { ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ """.trimIndent() - private val MISSING_SECRET_PROPS_ERROR_MSG = """ + private val missingSecretPropsErrorMsg = """ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⚠️ WARNING ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Your project does not contain a '$SECRETS_FILE' file. Roky requires this file to run locally as it contains - information about your local Supabase-CLI setup. + Your project either does not contain a '$SECRETS_PROPERTIES_FILE' file, or that file is empty. Roky requires this file + to run locally as it contains information about your local Supabase-CLI setup. - To build Roky, create a file named '$SECRETS_FILE' in the root directory of this project and add the values + To build Roky, create a file named '$SECRETS_PROPERTIES_FILE' in the root directory of this project and add the values specified in the Roky Wiki page under 'Project Setup' → 'Setup Supabase CLI': > https://github.com/PPartisan/Roky/wiki/Project-Setup#3-setup-supabase-cli - Alternatively, to use source code mocks, set 'USE_LOCAL_MOCKS=true' under 'gradle.properties'. + Your '$SECRETS_PROPERTIES_FILE' must contain these variables at a minimum: + ${listOf(KEY_CLIENT_KEY, KEY_SERVER_URL)} + + Alternatively, to use source code mocks, set '$KEY_USE_LOCAL_MOCKS=true' under '$GRADLE_PROPERTIES_FILE'. + + This task will generate minimal default values to compile. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ """.trimIndent() + private fun Map<*,*>.asProperties() : Properties = + Properties().apply { putAll(this@asProperties) } + + private fun Provider.createFile(file: String, block: File.() -> Unit) = + get().asFile.resolve("$file.kt").apply(block) + + private fun Properties.isValid() : Boolean { + if(isEmpty) + return false + return containsKey(KEY_CLIENT_KEY) && containsKey(KEY_SERVER_URL) + } + + private fun onUseLocalMocks() : List { + println(useLocalMocksMsg) + return defaults() + } + + private fun onUseRemoteButNotSecrets() : List { + println(missingSecretPropsErrorMsg) + return defaults() + } + private fun File.asProps() : Properties = Properties().apply { if(exists()) inputStream().use { load(it) } } + private fun defaults() : List = Properties().apply { + this[KEY_USE_LOCAL_MOCKS] = true + listOf(KEY_SERVER_URL, KEY_CLIENT_KEY).forEach { this[it] = "" } + }.let(::listOf) + private fun Map<*,*>.isTrue() = this[KEY_USE_LOCAL_MOCKS]?.toString()?.toBooleanStrictOrNull()?:false - private fun Map<*,*>.isFalse() = - !isTrue() - private fun Set>.asConstants() : String = buildString { + private fun Collection.asText() : String = + flatMap { it.entries } + .fold(setOf>()) { acc, it -> acc + setOf(it.toStringPair()) } + .asText() + + private fun Set>.asText() : String = buildString { header() - this@asConstants.forEach { appendLine(it.toConstant()) } + this@asText.forEach { appendLine(it.toConstant()) } footer() } diff --git a/build.gradle.kts b/build.gradle.kts index 268c144..e5da189 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ import org.jlleitschuh.gradle.ktlint.reporter.ReporterType.* plugins { - kotlin("jvm") version "1.9.23" + kotlin("jvm") version "2.1.0" id("org.jetbrains.kotlinx.kover").version("0.8.3") id("io.gitlab.arturbosch.detekt").version("1.23.3") id("org.jlleitschuh.gradle.ktlint").version("12.1.2") @@ -32,8 +32,15 @@ dependencies { val ktorVersion = "3.0.0" implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") + implementation("io.ktor:ktor-client-java:$ktorVersion") implementation("io.ktor:ktor-client-logging:$ktorVersion") + val supabaseVersion = "3.1.1" + implementation(platform("io.github.jan-tennert.supabase:bom:$supabaseVersion")) + implementation("io.github.jan-tennert.supabase:postgrest-kt") + implementation("io.github.jan-tennert.supabase:auth-kt") + implementation("io.github.jan-tennert.supabase:realtime-kt") + implementation("org.slf4j:slf4j-simple:2.0.16") implementation("com.googlecode.lanterna:lanterna:3.1.1") diff --git a/src/main/kotlin/main.kt b/src/main/kotlin/main.kt index d272261..30c0cb5 100644 --- a/src/main/kotlin/main.kt +++ b/src/main/kotlin/main.kt @@ -6,6 +6,12 @@ import com.googlecode.lanterna.gui2.MultiWindowTextGUI import com.googlecode.lanterna.screen.Screen import com.googlecode.lanterna.terminal.DefaultTerminalFactory import help.helpModule +import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.auth.Auth +import io.github.jan.supabase.createSupabaseClient +import io.github.jan.supabase.logging.LogLevel.INFO +import io.github.jan.supabase.postgrest.Postgrest +import io.github.jan.supabase.realtime.Realtime import io.ktor.client.HttpClient import io.ktor.client.HttpClientConfig import io.ktor.client.engine.cio.CIO @@ -54,6 +60,14 @@ val mainModules = factoryOf(::NavigationController) binds arrayOf(NavigateToAppWindow::class, NavigateToMainMenu::class) singleOf(::StartApp) singleOf(::StopApp) + single { + createSupabaseClient(Secrets.SERVER_URL, Secrets.CLIENT_KEY) { + defaultLogLevel = INFO + install(Auth) + install(Postgrest) + install(Realtime) + } + } } fun HttpClientConfig<*>.default() {