diff --git a/.idea/artifacts/template_js_1_0_0_SNAPSHOT.xml b/.idea/artifacts/template_js_1_0_0_SNAPSHOT.xml
index 5f495ea..fffbaa8 100644
--- a/.idea/artifacts/template_js_1_0_0_SNAPSHOT.xml
+++ b/.idea/artifacts/template_js_1_0_0_SNAPSHOT.xml
@@ -1,6 +1,8 @@
$PROJECT_DIR$/build/libs
-
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/template_jvm_1_0_0_SNAPSHOT.xml b/.idea/artifacts/template_jvm_1_0_0_SNAPSHOT.xml
index 5782466..d071410 100644
--- a/.idea/artifacts/template_jvm_1_0_0_SNAPSHOT.xml
+++ b/.idea/artifacts/template_jvm_1_0_0_SNAPSHOT.xml
@@ -1,6 +1,8 @@
$PROJECT_DIR$/build/libs
-
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 1267b7e..af3b15e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
plugins {
- kotlin("multiplatform") version "2.0.21"
+ kotlin("multiplatform") version "2.1.0"
}
group = "nl.astraeus"
@@ -19,9 +19,7 @@ repositories {
kotlin {
jvmToolchain(17)
- jvm {
- withJava()
- }
+ jvm()
js {
binaries.executable()
browser {
@@ -33,6 +31,7 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
+ api("nl.astraeus:kotlin-simple-logging:1.1.1")
api("nl.astraeus:kotlin-css-generator:1.0.10")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
diff --git a/src/jvmMain/kotlin/tmpl/Main.kt b/src/jvmMain/kotlin/tmpl/Main.kt
new file mode 100644
index 0000000..3133194
--- /dev/null
+++ b/src/jvmMain/kotlin/tmpl/Main.kt
@@ -0,0 +1,37 @@
+package tmpl
+
+import com.zaxxer.hikari.HikariConfig
+import nl.astraeus.logger.Logger
+import tmpl.db.Database
+
+val log = Logger()
+
+fun main() {
+ Thread.currentThread().uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
+ log.warn(e) {
+ e.message
+ }
+ }
+
+ Runtime.getRuntime().addShutdownHook(
+ object : Thread() {
+ override fun run() {
+ Database.vacuumDatabase()
+ Database.closeDatabase()
+ }
+ }
+ )
+
+ Class.forName("nl.astraeus.jdbc.Driver")
+ Database.initialize(HikariConfig().apply {
+ driverClassName = "nl.astraeus.jdbc.Driver"
+ jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/${REPO_NAME}.db"
+ username = "sa"
+ password = ""
+ maximumPoolSize = 25
+ isAutoCommit = false
+
+ validate()
+ })
+
+}
diff --git a/src/jvmMain/kotlin/tmpl/db/Database.kt b/src/jvmMain/kotlin/tmpl/db/Database.kt
new file mode 100644
index 0000000..b00ec1f
--- /dev/null
+++ b/src/jvmMain/kotlin/tmpl/db/Database.kt
@@ -0,0 +1,101 @@
+package tmpl.db
+
+import com.zaxxer.hikari.HikariConfig
+import com.zaxxer.hikari.HikariDataSource
+import java.sql.Connection
+import java.util.*
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.collections.set
+import kotlin.use
+
+private val currentConnection = ThreadLocal()
+
+fun transaction(
+ block: (Connection) -> T
+): T {
+ val hasConnection = currentConnection.get() != null
+ var oldConnection: Connection? = null
+
+ if (!hasConnection) {
+ currentConnection.set(Database.getConnection())
+/*
+ } else if (scope == TxScope.REQUIRES_NEW) {
+ oldConnection = currentConnection.get()
+
+ currentConnection.set(Database.getConnection())
+*/
+ }
+
+ val connection = currentConnection.get()
+
+ try {
+ val result = block(connection)
+
+ connection.commit()
+
+ return result
+ } finally {
+ if (!hasConnection) {
+ currentConnection.set(oldConnection)
+ connection.close()
+ }
+ }
+}
+
+object Database {
+
+ var ds: HikariDataSource? = null
+
+ fun initialize(config: HikariConfig) {
+ val properties = Properties()
+ properties["journal_mode"] = "WAL"
+
+ config.dataSourceProperties = properties
+ config.addDataSourceProperty("cachePrepStmts", "true")
+ config.addDataSourceProperty("prepStmtCacheSize", "250")
+ config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
+
+ ds = HikariDataSource(config)
+ Migrations.databaseVersionTableCreated = AtomicBoolean(false)
+ Migrations.updateDatabaseIfNeeded()
+ }
+
+ fun getConnection() = ds?.connection ?: error("Database has not been initialized!")
+
+ fun vacuumDatabase() {
+ getConnection().use {
+ it.autoCommit = true
+
+ it.prepareStatement("VACUUM").use { ps ->
+ ps.executeUpdate()
+ }
+ }
+ }
+
+ fun closeDatabase() {
+ ds?.close()
+ }
+
+ init {
+ val properties = Properties()
+ properties["journal_mode"] = "WAL"
+
+ val config = HikariConfig().apply {
+ driverClassName = "nl.astraeus.jdbc.Driver"
+ jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/daw3.db"
+ username = "sa"
+ password = ""
+ maximumPoolSize = 25
+ isAutoCommit = false
+ dataSourceProperties = properties
+ validate()
+ }
+
+ config.addDataSourceProperty("cachePrepStmts", "true")
+ config.addDataSourceProperty("prepStmtCacheSize", "250")
+ config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
+
+ ds = HikariDataSource(config)
+ }
+
+}
diff --git a/src/jvmMain/kotlin/tmpl/db/Migrations.kt b/src/jvmMain/kotlin/tmpl/db/Migrations.kt
new file mode 100644
index 0000000..ce68eb2
--- /dev/null
+++ b/src/jvmMain/kotlin/tmpl/db/Migrations.kt
@@ -0,0 +1,105 @@
+package tmpl.db
+
+import tmpl.log
+import java.sql.Connection
+import java.sql.SQLException
+import java.sql.Timestamp
+import java.util.concurrent.atomic.AtomicBoolean
+
+sealed class Migration {
+ class Query(
+ val query: String
+ ) : Migration() {
+ override fun toString(): String {
+ return query
+ }
+ }
+
+ class Code(
+ val code: (Connection) -> Unit
+ ) : Migration() {
+ override fun toString(): String {
+ return code.toString()
+ }
+ }
+}
+
+val DATABASE_MIGRATIONS = arrayOf(
+ Migration.Query(
+ """
+ CREATE TABLE DATABASE_VERSION (
+ ID INTEGER PRIMARY KEY,
+ QUERY TEXT,
+ EXECUTED TIMESTAMP
+ )
+ """.trimIndent()
+ ),
+ Migration.Query("SELECT sqlite_version()"),
+)
+
+object Migrations {
+ var databaseVersionTableCreated = AtomicBoolean(false)
+
+ fun updateDatabaseIfNeeded() {
+ try {
+ transaction { con ->
+ con.prepareStatement(
+ """
+ SELECT MAX(ID) FROM DATABASE_VERSION
+ """.trimIndent()
+ ).use { ps ->
+ ps.executeQuery().use { rs ->
+ databaseVersionTableCreated.compareAndSet(false, true)
+
+ if(rs.next()) {
+ val maxId = rs.getInt(1)
+
+ for (index in maxId + 1..
+ log.debug {
+ "Executing migration $index - [${DATABASE_MIGRATIONS[index]}]"
+ }
+ val description = when(
+ val migration = DATABASE_MIGRATIONS[index]
+ ) {
+ is Migration.Query -> {
+ con.prepareStatement(migration.query).use { ps ->
+ ps.execute()
+ }
+
+ migration.query
+ }
+ is Migration.Code -> {
+ migration.code(con)
+
+ migration.code.toString()
+ }
+ }
+ con.prepareStatement("INSERT INTO DATABASE_VERSION VALUES (?, ?, ?)").use { ps ->
+ ps.setInt(1, index)
+ ps.setString(2, description)
+ ps.setTimestamp(3, Timestamp(System.currentTimeMillis()))
+
+ ps.execute()
+ }
+ }
+ }
+
+}