feat: initial db persistence

This commit is contained in:
Finn Linck Ryan 2025-12-13 17:09:08 +01:00
parent e8abfb18eb
commit 2c4995f9d7
9 changed files with 342 additions and 46 deletions

View file

@ -1,7 +1,10 @@
package at.dokkae.homepage
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 io.github.cdimascio.dotenv.dotenv
import org.http4k.core.HttpHandler
import org.http4k.core.Method.*
import org.http4k.core.Response
@ -22,30 +25,43 @@ import org.http4k.sse.SseResponse
import org.http4k.template.JTETemplates
import org.http4k.template.ViewModel
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.util.UUID
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.concurrent.thread
data class Message (
data class Message(
val author: String,
val content: String,
val createdAt: Instant = Instant.now(),
val id: UUID = UUID.randomUUID(),
val createdAt: Instant = Instant.now(),
val updatedAt: Instant? = null
) : 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"
}
fun main() {
val messages = CopyOnWriteArrayList<Message>()
val subscribers = CopyOnWriteArrayList<Sse>()
val renderer = JTETemplates().Precompiled("build/classes/jte")
val env = dotenv()
messages.push(Message("Kurisu", "Hello World!"))
messages.push(Message("Violet", "Haii Kurisu!"))
val connection = DriverManager.getConnection(env["DB_URL"], env["DB_USERNAME"], env["DB_PASSWORD"])
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 = {
Response(Status.OK).body(renderer(Index(messages)))
Response(Status.OK).body(renderer(Index(messageRepository.findAll())))
}
val sse = sse(
@ -72,12 +88,12 @@ fun main() {
val msg = Message(author, message)
val sseMsg = SseMessage.Data(renderer(msg))
messages.push(msg)
messageRepository.save(msg)
subscribers.forEach {
thread { it.send(sseMsg) }
}
Response(Status.OK)
Response(Status.CREATED)
}
}
)

View file

@ -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>
}

View file

@ -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(),
)
}

View file

@ -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;

View 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();