feat: initial db persistence
This commit is contained in:
parent
e8abfb18eb
commit
2c4995f9d7
9 changed files with 342 additions and 46 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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