feat: initial db persistence
This commit is contained in:
parent
e8abfb18eb
commit
2c4995f9d7
9 changed files with 342 additions and 46 deletions
4
.env.example
Normal file
4
.env.example
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
DB_URL=jdbc:postgresql://localhost:5432/homepage
|
||||||
|
DB_MIGRATION=src/main/resources/db/migration
|
||||||
|
DB_USERNAME=postgres
|
||||||
|
DB_PASSWORD=postgres
|
||||||
184
build.gradle.kts
184
build.gradle.kts
|
|
@ -1,14 +1,42 @@
|
||||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
|
import io.github.klahap.dotenv.DotEnvBuilder
|
||||||
|
import org.flywaydb.gradle.task.FlywayMigrateTask
|
||||||
import org.gradle.api.JavaVersion.VERSION_21
|
import org.gradle.api.JavaVersion.VERSION_21
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
plugins {
|
val env = DotEnvBuilder.dotEnv {
|
||||||
kotlin("jvm") version "2.2.20"
|
addFile("$rootDir/.env")
|
||||||
|
addSystemEnv()
|
||||||
|
}
|
||||||
|
|
||||||
id("gg.jte.gradle") version "3.2.1"
|
val envDbUrl: String = env["DB_URL"] ?: ""
|
||||||
id("com.gradleup.shadow") version "9.3.0"
|
val envDbMigration: String = env["DB_MIGRATIONS"] ?: "src/main/resources/db/migration"
|
||||||
|
val envDbUsername: String = env["DB_USERNAME"] ?: ""
|
||||||
|
val envDbPassword: String = env["DB_PASSWORD"] ?: ""
|
||||||
|
|
||||||
|
val generatedResourcesDirectory = "${layout.buildDirectory.get()}/generated-resources"
|
||||||
|
val generatedSourcesDirectory = "${layout.buildDirectory.get()}/generated-src"
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlin.jvm)
|
||||||
|
alias(libs.plugins.shadow)
|
||||||
|
alias(libs.plugins.dotenv.plugin)
|
||||||
|
alias(libs.plugins.jte)
|
||||||
|
alias(libs.plugins.flyway)
|
||||||
|
alias(libs.plugins.jooq.codegen.gradle)
|
||||||
|
alias(libs.plugins.taskinfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain {
|
||||||
|
languageVersion.set(JavaLanguageVersion.of(21))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
|
@ -18,32 +46,20 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
classpath(libs.postgresql)
|
||||||
|
classpath(libs.flyway.database.postgresql)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvmToolchain {
|
|
||||||
languageVersion.set(JavaLanguageVersion.of(21))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jte {
|
|
||||||
sourceDirectory.set(Path("src/main/kte"))
|
|
||||||
targetDirectory.set(Path("${layout.buildDirectory.get()}/classes/jte"))
|
|
||||||
|
|
||||||
precompile()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets.main {
|
sourceSets.main {
|
||||||
resources.srcDir("${layout.buildDirectory.get()}/classes/jte")
|
resources.srcDir("$generatedResourcesDirectory/jte")
|
||||||
}
|
kotlin.srcDir("$generatedSourcesDirectory/jooq")
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
withType<KotlinJvmCompile>().configureEach {
|
withType<KotlinJvmCompile>().configureEach {
|
||||||
|
dependsOn("jooqCodegen")
|
||||||
|
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
allWarningsAsErrors = false
|
allWarningsAsErrors = false
|
||||||
jvmTarget.set(JVM_21)
|
jvmTarget.set(JVM_21)
|
||||||
|
|
@ -55,6 +71,14 @@ tasks {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withType<FlywayMigrateTask> {
|
||||||
|
dependsOn("initDb")
|
||||||
|
}
|
||||||
|
|
||||||
|
named("precompileJte") {
|
||||||
|
dependsOn("compileKotlin")
|
||||||
|
}
|
||||||
|
|
||||||
named<ShadowJar>("shadowJar") {
|
named<ShadowJar>("shadowJar") {
|
||||||
manifest {
|
manifest {
|
||||||
attributes("Main-Class" to "at.dokkae.homepage.HomepageKt")
|
attributes("Main-Class" to "at.dokkae.homepage.HomepageKt")
|
||||||
|
|
@ -62,13 +86,19 @@ tasks {
|
||||||
|
|
||||||
dependsOn("precompileJte")
|
dependsOn("precompileJte")
|
||||||
|
|
||||||
from("${layout.buildDirectory.get()}/classes/jte")
|
mustRunAfter("flywayMigrate", "jooqCodegen")
|
||||||
|
|
||||||
|
from("$generatedResourcesDirectory/jte")
|
||||||
|
|
||||||
archiveFileName.set("app.jar")
|
archiveFileName.set("app.jar")
|
||||||
|
|
||||||
exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")
|
exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register("buildDocker") {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = VERSION_21
|
sourceCompatibility = VERSION_21
|
||||||
targetCompatibility = VERSION_21
|
targetCompatibility = VERSION_21
|
||||||
|
|
@ -76,17 +106,101 @@ tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(platform("org.http4k:http4k-bom:6.23.1.0"))
|
implementation(platform(libs.http4k.bom))
|
||||||
implementation("org.http4k:http4k-client-okhttp")
|
|
||||||
implementation("org.http4k:http4k-core")
|
implementation(libs.dotenv)
|
||||||
implementation("org.http4k:http4k-server-jetty")
|
|
||||||
implementation("org.http4k:http4k-template-jte")
|
implementation(libs.bundles.http4k)
|
||||||
implementation("org.http4k:http4k-web-htmx")
|
implementation(libs.jte.kotlin)
|
||||||
implementation("gg.jte:jte-kotlin:3.2.1")
|
implementation(libs.bundles.database)
|
||||||
testImplementation("org.http4k:http4k-testing-approval")
|
|
||||||
testImplementation("org.http4k:http4k-testing-hamkrest")
|
testImplementation(libs.bundles.testing)
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-api:6.0.0")
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.0")
|
jooqCodegen(libs.jooq.meta)
|
||||||
testImplementation("org.junit.platform:junit-platform-launcher:6.0.0")
|
jooqCodegen(libs.jooq.postgres)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== JTE Templating ==========
|
||||||
|
jte {
|
||||||
|
sourceDirectory.set(Path("src/main/kte"))
|
||||||
|
targetDirectory.set(Path("$generatedResourcesDirectory/jte"))
|
||||||
|
precompile()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== FlyWay ==========
|
||||||
|
flyway {
|
||||||
|
url = envDbUrl
|
||||||
|
user = envDbUsername
|
||||||
|
password = envDbPassword
|
||||||
|
locations = arrayOf("filesystem:$envDbMigration")
|
||||||
|
baselineOnMigrate = true
|
||||||
|
validateMigrationNaming = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("initDb") {
|
||||||
|
doFirst {
|
||||||
|
println("Database Configuration:")
|
||||||
|
println(" Raw URL from env: $envDbUrl")
|
||||||
|
println(" Resolved URL: $envDbUrl")
|
||||||
|
println(" Migrations: $envDbMigration")
|
||||||
|
println(" Credentials:")
|
||||||
|
println(" Username: $envDbUsername")
|
||||||
|
println(" Password: ${"*".repeat(envDbPassword.length)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named("flywayMigrate") {
|
||||||
|
finalizedBy("jooqCodegen")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Jooq ==========
|
||||||
|
jooq {
|
||||||
|
configuration {
|
||||||
|
logging = org.jooq.meta.jaxb.Logging.WARN
|
||||||
|
|
||||||
|
jdbc {
|
||||||
|
driver = "org.postgresql.Driver"
|
||||||
|
url = envDbUrl
|
||||||
|
user = envDbUsername
|
||||||
|
password = envDbPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
generator {
|
||||||
|
name = "org.jooq.codegen.KotlinGenerator"
|
||||||
|
|
||||||
|
database {
|
||||||
|
name = "org.jooq.meta.postgres.PostgresDatabase"
|
||||||
|
inputSchema = "public"
|
||||||
|
|
||||||
|
// SQLite specific configuration
|
||||||
|
includes = ".*"
|
||||||
|
excludes = """
|
||||||
|
flyway_.*|
|
||||||
|
pg_.*|
|
||||||
|
information_schema.*
|
||||||
|
""".trimMargin().replace("\n", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
generate {
|
||||||
|
// Recommended settings for Kotlin
|
||||||
|
isDeprecated = false
|
||||||
|
isRecords = true
|
||||||
|
isImmutablePojos = true
|
||||||
|
isFluentSetters = true
|
||||||
|
isKotlinNotNullRecordAttributes = true
|
||||||
|
isKotlinNotNullPojoAttributes = true
|
||||||
|
isKotlinNotNullInterfaceAttributes = true
|
||||||
|
isPojosAsKotlinDataClasses = true
|
||||||
|
}
|
||||||
|
|
||||||
|
target {
|
||||||
|
packageName = "at.dokkae.homepage.generated.jooq"
|
||||||
|
directory = "$generatedSourcesDirectory/jooq"
|
||||||
|
}
|
||||||
|
|
||||||
|
strategy {
|
||||||
|
name = "org.jooq.codegen.DefaultGeneratorStrategy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.configuration-cache=true
|
org.gradle.configuration-cache=false
|
||||||
83
gradle/libs.versions.toml
Normal file
83
gradle/libs.versions.toml
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
[versions]
|
||||||
|
kotlin = "2.2.20"
|
||||||
|
shadow = "9.3.0"
|
||||||
|
dotenv-plugin = "1.1.3"
|
||||||
|
dotenv = "6.5.1"
|
||||||
|
http4k = "6.23.1.0"
|
||||||
|
jte = "3.2.1"
|
||||||
|
flyway = "11.19.0"
|
||||||
|
jooq = "3.20.10"
|
||||||
|
junit = "6.0.0"
|
||||||
|
postgresql = "42.7.7"
|
||||||
|
taskinfo = "3.0.0"
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
|
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
|
||||||
|
dotenv-plugin = { id = "io.github.klahap.dotenv", version.ref = "dotenv-plugin" }
|
||||||
|
jte = { id = "gg.jte.gradle", version.ref = "jte" }
|
||||||
|
flyway = { id = "org.flywaydb.flyway", version.ref = "flyway" }
|
||||||
|
jooq-codegen-gradle = { id = "org.jooq.jooq-codegen-gradle", version.ref = "jooq" }
|
||||||
|
taskinfo = { id = "org.barfuin.gradle.taskinfo", version.ref = "taskinfo" }
|
||||||
|
|
||||||
|
[bundles]
|
||||||
|
http4k = [
|
||||||
|
"http4k-client-okhttp",
|
||||||
|
"http4k-core",
|
||||||
|
"http4k-server-jetty",
|
||||||
|
"http4k-template-jte",
|
||||||
|
"http4k-web-htmx"
|
||||||
|
]
|
||||||
|
|
||||||
|
testing = [
|
||||||
|
"http4k-testing-approval",
|
||||||
|
"http4k-testing-hamkrest",
|
||||||
|
"junit-jupiter-api",
|
||||||
|
"junit-jupiter-engine",
|
||||||
|
"junit-platform-launcher"
|
||||||
|
]
|
||||||
|
|
||||||
|
database = [
|
||||||
|
"flyway-core",
|
||||||
|
"jooq",
|
||||||
|
"jooq-meta",
|
||||||
|
"jooq-codegen",
|
||||||
|
"jooq-postgres"
|
||||||
|
]
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
# Environment Management
|
||||||
|
dotenv = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" }
|
||||||
|
|
||||||
|
# HTTP4K Platform (BOM)
|
||||||
|
http4k-bom = { module = "org.http4k:http4k-bom", version.ref = "http4k" }
|
||||||
|
|
||||||
|
# HTTP4K Dependencies
|
||||||
|
http4k-client-okhttp = { module = "org.http4k:http4k-client-okhttp" }
|
||||||
|
http4k-core = { module = "org.http4k:http4k-core" }
|
||||||
|
http4k-server-jetty = { module = "org.http4k:http4k-server-jetty" }
|
||||||
|
http4k-template-jte = { module = "org.http4k:http4k-template-jte" }
|
||||||
|
http4k-web-htmx = { module = "org.http4k:http4k-web-htmx" }
|
||||||
|
|
||||||
|
# JTE Templating
|
||||||
|
jte-kotlin = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
||||||
|
|
||||||
|
# Database Driver
|
||||||
|
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
|
||||||
|
|
||||||
|
# Flyway
|
||||||
|
flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway" }
|
||||||
|
flyway-database-postgresql = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway"}
|
||||||
|
|
||||||
|
# Jooq
|
||||||
|
jooq = { module = "org.jooq:jooq", version.ref = "jooq" }
|
||||||
|
jooq-meta = { module = "org.jooq:jooq-meta", version.ref = "jooq" }
|
||||||
|
jooq-codegen = { module = "org.jooq:jooq-codegen", version.ref = "jooq" }
|
||||||
|
jooq-postgres = { module = "org.jooq:jooq-postgres-extensions", version.ref = "jooq" }
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
http4k-testing-approval = { module = "org.http4k:http4k-testing-approval" }
|
||||||
|
http4k-testing-hamkrest = { module = "org.http4k:http4k-testing-hamkrest" }
|
||||||
|
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
|
||||||
|
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
|
||||||
|
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit" }
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package at.dokkae.homepage
|
package at.dokkae.homepage
|
||||||
|
|
||||||
import at.dokkae.homepage.extensions.Precompiled
|
import at.dokkae.homepage.extensions.Precompiled
|
||||||
|
import at.dokkae.homepage.repository.MessageRepository
|
||||||
|
import at.dokkae.homepage.repository.impls.JooqMessageRepository
|
||||||
import at.dokkae.homepage.templates.Index
|
import at.dokkae.homepage.templates.Index
|
||||||
|
import io.github.cdimascio.dotenv.dotenv
|
||||||
import org.http4k.core.HttpHandler
|
import org.http4k.core.HttpHandler
|
||||||
import org.http4k.core.Method.*
|
import org.http4k.core.Method.*
|
||||||
import org.http4k.core.Response
|
import org.http4k.core.Response
|
||||||
|
|
@ -22,30 +25,43 @@ import org.http4k.sse.SseResponse
|
||||||
import org.http4k.template.JTETemplates
|
import org.http4k.template.JTETemplates
|
||||||
import org.http4k.template.ViewModel
|
import org.http4k.template.ViewModel
|
||||||
import org.jetbrains.kotlin.backend.common.push
|
import org.jetbrains.kotlin.backend.common.push
|
||||||
|
import org.jooq.SQLDialect
|
||||||
|
import org.jooq.impl.DSL
|
||||||
|
import java.sql.DriverManager
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
data class Message (
|
data class Message(
|
||||||
val author: String,
|
val author: String,
|
||||||
val content: String,
|
val content: String,
|
||||||
val createdAt: Instant = Instant.now(),
|
|
||||||
val id: UUID = UUID.randomUUID(),
|
val id: UUID = UUID.randomUUID(),
|
||||||
|
val createdAt: Instant = Instant.now(),
|
||||||
|
val updatedAt: Instant? = null
|
||||||
) : ViewModel {
|
) : ViewModel {
|
||||||
|
init {
|
||||||
|
require(author.length <= 31) { "Author must be 31 characters or less" }
|
||||||
|
require(content.length <= 255) { "Content must be 255 characters or less" }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun template(): String = "partials/Message"
|
override fun template(): String = "partials/Message"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
val messages = CopyOnWriteArrayList<Message>()
|
val env = dotenv()
|
||||||
val subscribers = CopyOnWriteArrayList<Sse>()
|
|
||||||
val renderer = JTETemplates().Precompiled("build/classes/jte")
|
|
||||||
|
|
||||||
messages.push(Message("Kurisu", "Hello World!"))
|
val connection = DriverManager.getConnection(env["DB_URL"], env["DB_USERNAME"], env["DB_PASSWORD"])
|
||||||
messages.push(Message("Violet", "Haii Kurisu!"))
|
val dslContext = DSL.using(connection, SQLDialect.POSTGRES)
|
||||||
|
val messageRepository: MessageRepository = JooqMessageRepository(dslContext)
|
||||||
|
|
||||||
|
val subscribers = CopyOnWriteArrayList<Sse>()
|
||||||
|
val renderer = JTETemplates().Precompiled("build/generated-resources/jte")
|
||||||
|
|
||||||
val indexHandler: HttpHandler = {
|
val indexHandler: HttpHandler = {
|
||||||
Response(Status.OK).body(renderer(Index(messages)))
|
Response(Status.OK).body(renderer(Index(messageRepository.findAll())))
|
||||||
}
|
}
|
||||||
|
|
||||||
val sse = sse(
|
val sse = sse(
|
||||||
|
|
@ -72,12 +88,12 @@ fun main() {
|
||||||
val msg = Message(author, message)
|
val msg = Message(author, message)
|
||||||
val sseMsg = SseMessage.Data(renderer(msg))
|
val sseMsg = SseMessage.Data(renderer(msg))
|
||||||
|
|
||||||
messages.push(msg)
|
messageRepository.save(msg)
|
||||||
subscribers.forEach {
|
subscribers.forEach {
|
||||||
thread { it.send(sseMsg) }
|
thread { it.send(sseMsg) }
|
||||||
}
|
}
|
||||||
|
|
||||||
Response(Status.OK)
|
Response(Status.CREATED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package at.dokkae.homepage.repository
|
||||||
|
|
||||||
|
import at.dokkae.homepage.Message
|
||||||
|
|
||||||
|
interface MessageRepository {
|
||||||
|
fun save(message: Message): Message
|
||||||
|
fun findAll(): List<Message>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package at.dokkae.homepage.repository.impls
|
||||||
|
|
||||||
|
import at.dokkae.homepage.Message
|
||||||
|
import at.dokkae.homepage.generated.jooq.tables.records.MessageRecord
|
||||||
|
import at.dokkae.homepage.generated.jooq.tables.references.MESSAGE
|
||||||
|
import at.dokkae.homepage.repository.MessageRepository
|
||||||
|
import org.jooq.DSLContext
|
||||||
|
import org.jooq.impl.DSL
|
||||||
|
|
||||||
|
class JooqMessageRepository(
|
||||||
|
private val dslContext: DSLContext
|
||||||
|
) : MessageRepository {
|
||||||
|
override fun save(message: Message): Message = dslContext.transactionResult { config ->
|
||||||
|
val ctx = DSL.using(config)
|
||||||
|
|
||||||
|
ctx.insertInto(MESSAGE)
|
||||||
|
.set(MESSAGE.ID, message.id)
|
||||||
|
.set(MESSAGE.AUTHOR, message.author)
|
||||||
|
.set(MESSAGE.CONTENT, message.content)
|
||||||
|
.onDuplicateKeyUpdate()
|
||||||
|
.set(MESSAGE.AUTHOR, message.author)
|
||||||
|
.set(MESSAGE.CONTENT, message.content)
|
||||||
|
.returning()
|
||||||
|
.fetchOne()!!
|
||||||
|
.toMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAll(): List<Message> = dslContext.selectFrom(MESSAGE)
|
||||||
|
.orderBy(MESSAGE.CREATED_AT.desc())
|
||||||
|
.fetch { it.toMessage() }
|
||||||
|
|
||||||
|
|
||||||
|
private fun MessageRecord.toMessage(): Message = Message(
|
||||||
|
id = this.id,
|
||||||
|
author = this.author,
|
||||||
|
content = this.content,
|
||||||
|
createdAt = this.createdAt!!.toInstant(),
|
||||||
|
updatedAt = this.updatedAt?.toInstant(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
create or replace function handle_timestamps()
|
||||||
|
returns trigger as
|
||||||
|
$$
|
||||||
|
begin
|
||||||
|
if lower(tg_op) = 'insert' then
|
||||||
|
new.created_at = coalesce(new.created_at, current_timestamp);
|
||||||
|
new.updated_at = coalesce(new.updated_at, current_timestamp);
|
||||||
|
elsif lower(tg_op) = 'update' then
|
||||||
|
if new.created_at is distinct from old.created_at then
|
||||||
|
raise exception 'Direct modification of created_at is not allowed.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
new.updated_at = current_timestamp;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return new;
|
||||||
|
end;
|
||||||
|
$$ language plpgsql;
|
||||||
13
src/main/resources/db/migration/V002__add_message_table.sql
Normal file
13
src/main/resources/db/migration/V002__add_message_table.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
create table message (
|
||||||
|
id uuid not null primary key,
|
||||||
|
author varchar(31) not null,
|
||||||
|
content varchar(255) not null,
|
||||||
|
|
||||||
|
created_at timestamptz not null default current_timestamp,
|
||||||
|
updated_at timestamptz
|
||||||
|
);
|
||||||
|
|
||||||
|
create trigger handle_message_timestamps
|
||||||
|
before insert or update on message
|
||||||
|
for each row
|
||||||
|
execute function handle_timestamps();
|
||||||
Loading…
Add table
Add a link
Reference in a new issue