diff --git a/.env.example b/.env.example index 71b5d1a..eb9c21b 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,5 @@ -APP_PORT=9000 -APP_DOMAIN=localhost -APP_ENV=development +PORT=9000 +HOST=localhost DB_URL=jdbc:postgresql://localhost:5432/homepage DB_USERNAME=postgres diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 68fca5a..428dfd3 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -21,7 +21,7 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - - 5432:5432 + - 5445:5432 options: >- --health-cmd="pg_isready -U postgres" --health-interval=5s @@ -39,8 +39,6 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - with: - driver-opts: network=host - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -62,29 +60,24 @@ jobs: type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}} + - name: Wait for PostgreSQL + run: | + until pg_isready -h localhost -p 5445 -U postgres; do + echo "Waiting for postgres..." + sleep 2 + done + - name: Build and publish Docker image uses: docker/build-push-action@v5 with: context: . push: true + network: host + build-args: | + DB_URL=jdbc:postgresql://localhost:5445/homepage + DB_USERNAME=postgres + DB_PASSWORD=postgres tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - - deploy: - runs-on: ubuntu-latest - needs: build - if: success() - - steps: - - name: Trigger Dokploy deployment - run: | - curl -X 'POST' \ - 'http://5.180.253.47:3000/api/application.deploy' \ - -H 'accept: application/json' \ - -H 'Content-Type: application/json' \ - -H 'x-api-key: ${{ secrets.DOKPLOY_API_KEY }}' \ - -d '{ - "applicationId": "${{ vars.DOKPLOY_APP_ID }}" - }' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1fee1ef..0e0ad97 100644 --- a/.gitignore +++ b/.gitignore @@ -56,10 +56,6 @@ gradle-app.setting # JDT-specific (Eclipse Java Development Tools) .classpath -# Allow generated code fragments for Docker builds -!build/generated-src/** -!build/generated-resources/** - ################### ### Environment ### ################### diff --git a/Dockerfile b/Dockerfile index 8e2c09b..8afe2ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,13 @@ # --- Stage 1: Build the JAR --- FROM gradle:9.2.1-jdk21 AS build +ARG DB_URL +ARG DB_USERNAME +ARG DB_PASSWORD +ENV DB_URL=${DB_URL} +ENV DB_USERNAME=${DB_USERNAME} +ENV DB_PASSWORD=${DB_PASSWORD} + # Set working dir WORKDIR /app @@ -11,12 +18,8 @@ COPY --chown=gradle:gradle gradle ./gradle # Copy source code COPY --chown=gradle:gradle src ./src -# Copy pre-generated code fragments -COPY --chown=gradle:gradle build/generated-src ./build/generated-src -COPY --chown=gradle:gradle build/generated-resources ./build/generated-resources - -# Build the fat jar without cleaning (preserves generated code) -RUN ./gradlew build -x clean -x cleanGenerated -x jooqCodegen -x flywayMigrate -x precompileJte --no-daemon +# Build the fat jar +RUN ./gradlew clean build --no-daemon # --- Stage 2: Run the app --- FROM eclipse-temurin:21-jdk-alpine diff --git a/build.gradle.kts b/build.gradle.kts index defbd59..2181e03 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -269,15 +269,10 @@ tasks.named("shadowJar") { archiveFileName.set("app.jar") - mergeServiceFiles { - include("META-INF/services/**") - - // Fix: https://github.com/flyway/flyway/issues/4170#issuecomment-3569762563 - duplicatesStrategy = DuplicatesStrategy.INCLUDE - } + mergeServiceFiles() exclude( - "META-INF/*.RSA", + "META-INF/*. RSA", "META-INF/*.SF", "META-INF/*.DSA" ) @@ -300,4 +295,8 @@ tasks.register("cleanGenerated") { delete(generatedSourcesDir) logger.lifecycle("✓ Cleaned generated code directories") } +} + +tasks.named("clean") { + dependsOn("cleanGenerated") } \ No newline at end of file diff --git a/build/generated-resources/jte/META-INF/main.kotlin_module b/build/generated-resources/jte/META-INF/main.kotlin_module deleted file mode 100644 index 9dbc290..0000000 Binary files a/build/generated-resources/jte/META-INF/main.kotlin_module and /dev/null differ diff --git a/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated$Companion.class b/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated$Companion.class deleted file mode 100644 index 448574b..0000000 Binary files a/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated$Companion.class and /dev/null differ diff --git a/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated.class b/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated.class deleted file mode 100644 index 4c47fad..0000000 Binary files a/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated.class and /dev/null differ diff --git a/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated.kt b/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated.kt deleted file mode 100644 index 5b8c1dc..0000000 --- a/build/generated-resources/jte/gg/jte/generated/precompiled/JteIndexGenerated.kt +++ /dev/null @@ -1,35 +0,0 @@ -@file:Suppress("ktlint") -package gg.jte.generated.precompiled -import at.dokkae.homepage.templates.IndexTemplate -import at.dokkae.homepage.templates.MessageTemplate -import gg.jte.support.ForSupport -@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") -class JteIndexGenerated { -companion object { - @JvmField val JTE_NAME = "Index.kte" - @JvmField val JTE_LINE_INFO = intArrayOf(0,0,0,1,2,4,4,4,4,4,18,18,37,58,88,93,96,96,97,97,98,98,102,108,119,132,146,161,161,161,4,4,4,4,4) - @JvmStatic fun render(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, model:IndexTemplate) { - jteOutput.writeContent("\n\n\n\n \n \n Dokkae's Chat\n\n \n \n \n\n \n\n\n
\n ") - jteOutput.writeContent("\n
\n

Dokkae's Chat

\n
\n\n ") - jteOutput.writeContent("\n
\n
\n ") - for (message in model.messages) { - jteOutput.writeContent("\n ") - gg.jte.generated.precompiled.partials.JteMessageGenerated.render(jteOutput, jteHtmlInterceptor, MessageTemplate(message)); - jteOutput.writeContent("\n ") - } - jteOutput.writeContent("\n
\n
\n\n ") - jteOutput.writeContent("\n
\n
\n ") - jteOutput.writeContent("\n
\n
\n \n
\n
\n\n ") - jteOutput.writeContent("\n
\n
\n
\n \n
\n\n ") - jteOutput.writeContent("\n \n
\n
\n
\n
\n\n ") - jteOutput.writeContent("\n \n
\n\n\n") - } - @JvmStatic fun renderMap(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, params:Map) { - val model = params["model"] as IndexTemplate - render(jteOutput, jteHtmlInterceptor, model); - } -} -} diff --git a/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated$Companion.class b/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated$Companion.class deleted file mode 100644 index 356b8f6..0000000 Binary files a/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated$Companion.class and /dev/null differ diff --git a/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated.class b/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated.class deleted file mode 100644 index a51102d..0000000 Binary files a/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated.class and /dev/null differ diff --git a/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated.kt b/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated.kt deleted file mode 100644 index f00a90c..0000000 --- a/build/generated-resources/jte/gg/jte/generated/precompiled/partials/JteMessageGenerated.kt +++ /dev/null @@ -1,46 +0,0 @@ -@file:Suppress("ktlint") -package gg.jte.generated.precompiled.partials -import at.dokkae.homepage.templates.MessageTemplate -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter -import kotlin.math.absoluteValue -@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") -class JteMessageGenerated { -companion object { - @JvmField val JTE_NAME = "partials/Message.kte" - @JvmField val JTE_LINE_INFO = intArrayOf(0,0,0,1,2,3,4,6,6,6,6,6,8,8,8,9,9,10,10,14,15,15,15,15,18,21,21,21,24,24,24,24,24,24,28,30,30,30,34,34,34,6,6,6,6,6) - @JvmStatic fun render(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, model:MessageTemplate) { - jteOutput.writeContent("\n") - val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy").withZone(ZoneId.systemDefault()) - jteOutput.writeContent("\n") - val timeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.systemDefault()) - jteOutput.writeContent("\n") - val borderColors = listOf("red", "orange", "yellow", "green", "blue", "pink" ) - jteOutput.writeContent("\n\n
\n
\n ") - jteOutput.writeContent("\n
\n\n
\n ") - jteOutput.writeContent("\n
\n \n ") - jteOutput.setContext("span", null) - jteOutput.writeUserContent(model.message.author) - jteOutput.writeContent("\n \n \n ") - jteOutput.setContext("span", null) - jteOutput.writeUserContent(dateFormatter.format(model.message.createdAt)) - jteOutput.writeContent(" • ") - jteOutput.setContext("span", null) - jteOutput.writeUserContent(timeFormatter.format(model.message.createdAt)) - jteOutput.writeContent("\n \n
\n\n ") - jteOutput.writeContent("\n
\n ") - jteOutput.setContext("div", null) - jteOutput.writeUserContent(model.message.content) - jteOutput.writeContent("\n
\n
\n
\n
") - } - @JvmStatic fun renderMap(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, params:Map) { - val model = params["model"] as MessageTemplate - render(jteOutput, jteHtmlInterceptor, model); - } -} -} diff --git a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/DefaultCatalog.kt b/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/DefaultCatalog.kt deleted file mode 100644 index 034f3de..0000000 --- a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/DefaultCatalog.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package at.dokkae.homepage.generated.jooq - - -import kotlin.collections.List - -import org.jooq.Constants -import org.jooq.Schema -import org.jooq.impl.CatalogImpl - - -/** - * This class is generated by jOOQ. - */ -@Suppress("warnings") -open class DefaultCatalog : CatalogImpl("") { - companion object { - - /** - * The reference instance of DEFAULT_CATALOG - */ - val DEFAULT_CATALOG: DefaultCatalog = DefaultCatalog() - } - - /** - * standard public schema - */ - val PUBLIC: Public get(): Public = Public.PUBLIC - - override fun getSchemas(): List = listOf( - Public.PUBLIC - ) - - /** - * A reference to the 3.20 minor release of the code generator. If this - * doesn't compile, it's because the runtime library uses an older minor - * release, namely: 3.20. You can turn off the generation of this reference - * by specifying /configuration/generator/generate/jooqVersionReference - */ - private val REQUIRE_RUNTIME_JOOQ_VERSION = Constants.VERSION_3_20 -} diff --git a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/Public.kt b/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/Public.kt deleted file mode 100644 index 772d0e4..0000000 --- a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/Public.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package at.dokkae.homepage.generated.jooq - - -import at.dokkae.homepage.generated.jooq.tables.Message - -import kotlin.collections.List - -import org.jooq.Catalog -import org.jooq.Table -import org.jooq.impl.DSL -import org.jooq.impl.SchemaImpl - - -/** - * standard public schema - */ -@Suppress("warnings") -open class Public : SchemaImpl(DSL.name("public"), DefaultCatalog.DEFAULT_CATALOG, DSL.comment("standard public schema")) { - companion object { - - /** - * The reference instance of public - */ - val PUBLIC: Public = Public() - } - - /** - * The table public.message. - */ - val MESSAGE: Message get() = Message.MESSAGE - - override fun getCatalog(): Catalog = DefaultCatalog.DEFAULT_CATALOG - - override fun getTables(): List> = listOf( - Message.MESSAGE - ) -} diff --git a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/keys/Keys.kt b/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/keys/Keys.kt deleted file mode 100644 index f59d310..0000000 --- a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/keys/Keys.kt +++ /dev/null @@ -1,21 +0,0 @@ -@file:Suppress("warnings") -/* - * This file is generated by jOOQ. - */ -package at.dokkae.homepage.generated.jooq.keys - - -import at.dokkae.homepage.generated.jooq.tables.Message -import at.dokkae.homepage.generated.jooq.tables.records.MessageRecord - -import org.jooq.UniqueKey -import org.jooq.impl.DSL -import org.jooq.impl.Internal - - - -// ------------------------------------------------------------------------- -// UNIQUE and PRIMARY KEY definitions -// ------------------------------------------------------------------------- - -val MESSAGE_PKEY: UniqueKey = Internal.createUniqueKey(Message.MESSAGE, DSL.name("message_pkey"), arrayOf(Message.MESSAGE.ID), true) diff --git a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/Message.kt b/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/Message.kt deleted file mode 100644 index 4801187..0000000 --- a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/Message.kt +++ /dev/null @@ -1,187 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package at.dokkae.homepage.generated.jooq.tables - - -import at.dokkae.homepage.generated.jooq.Public -import at.dokkae.homepage.generated.jooq.keys.MESSAGE_PKEY -import at.dokkae.homepage.generated.jooq.tables.records.MessageRecord - -import java.time.OffsetDateTime -import java.util.UUID - -import kotlin.collections.Collection - -import org.jooq.Condition -import org.jooq.Field -import org.jooq.ForeignKey -import org.jooq.InverseForeignKey -import org.jooq.Name -import org.jooq.PlainSQL -import org.jooq.QueryPart -import org.jooq.Record -import org.jooq.SQL -import org.jooq.Schema -import org.jooq.Select -import org.jooq.Stringly -import org.jooq.Table -import org.jooq.TableField -import org.jooq.TableOptions -import org.jooq.UniqueKey -import org.jooq.impl.DSL -import org.jooq.impl.SQLDataType -import org.jooq.impl.TableImpl - - -/** - * This class is generated by jOOQ. - */ -@Suppress("warnings") -open class Message( - alias: Name, - path: Table?, - childPath: ForeignKey?, - parentPath: InverseForeignKey?, - aliased: Table?, - parameters: Array?>?, - where: Condition? -): TableImpl( - alias, - Public.PUBLIC, - path, - childPath, - parentPath, - aliased, - parameters, - DSL.comment(""), - TableOptions.table(), - where, -) { - companion object { - - /** - * The reference instance of public.message - */ - val MESSAGE: Message = Message() - } - - /** - * The class holding records for this type - */ - override fun getRecordType(): Class = MessageRecord::class.java - - /** - * The column public.message.id. - */ - val ID: TableField = createField(DSL.name("id"), SQLDataType.UUID.nullable(false), this, "") - - /** - * The column public.message.author. - */ - val AUTHOR: TableField = createField(DSL.name("author"), SQLDataType.VARCHAR(31).nullable(false), this, "") - - /** - * The column public.message.content. - */ - val CONTENT: TableField = createField(DSL.name("content"), SQLDataType.VARCHAR(255).nullable(false), this, "") - - /** - * The column public.message.created_at. - */ - val CREATED_AT: TableField = createField(DSL.name("created_at"), SQLDataType.TIMESTAMPWITHTIMEZONE(6).nullable(false).defaultValue(DSL.field(DSL.raw("CURRENT_TIMESTAMP"), SQLDataType.TIMESTAMPWITHTIMEZONE)), this, "") - - /** - * The column public.message.updated_at. - */ - val UPDATED_AT: TableField = createField(DSL.name("updated_at"), SQLDataType.TIMESTAMPWITHTIMEZONE(6), this, "") - - private constructor(alias: Name, aliased: Table?): this(alias, null, null, null, aliased, null, null) - private constructor(alias: Name, aliased: Table?, parameters: Array?>?): this(alias, null, null, null, aliased, parameters, null) - private constructor(alias: Name, aliased: Table?, where: Condition?): this(alias, null, null, null, aliased, null, where) - - /** - * Create an aliased public.message table reference - */ - constructor(alias: String): this(DSL.name(alias)) - - /** - * Create an aliased public.message table reference - */ - constructor(alias: Name): this(alias, null) - - /** - * Create a public.message table reference - */ - constructor(): this(DSL.name("message"), null) - override fun getSchema(): Schema? = if (aliased()) null else Public.PUBLIC - override fun getPrimaryKey(): UniqueKey = MESSAGE_PKEY - override fun `as`(alias: String): Message = Message(DSL.name(alias), this) - override fun `as`(alias: Name): Message = Message(alias, this) - override fun `as`(alias: Table<*>): Message = Message(alias.qualifiedName, this) - - /** - * Rename this table - */ - override fun rename(name: String): Message = Message(DSL.name(name), null) - - /** - * Rename this table - */ - override fun rename(name: Name): Message = Message(name, null) - - /** - * Rename this table - */ - override fun rename(name: Table<*>): Message = Message(name.qualifiedName, null) - - /** - * Create an inline derived table from this table - */ - override fun where(condition: Condition?): Message = Message(qualifiedName, if (aliased()) this else null, condition) - - /** - * Create an inline derived table from this table - */ - override fun where(conditions: Collection): Message = where(DSL.and(conditions)) - - /** - * Create an inline derived table from this table - */ - override fun where(vararg conditions: Condition?): Message = where(DSL.and(*conditions)) - - /** - * Create an inline derived table from this table - */ - override fun where(condition: Field?): Message = where(DSL.condition(condition)) - - /** - * Create an inline derived table from this table - */ - @PlainSQL override fun where(condition: SQL): Message = where(DSL.condition(condition)) - - /** - * Create an inline derived table from this table - */ - @PlainSQL override fun where(@Stringly.SQL condition: String): Message = where(DSL.condition(condition)) - - /** - * Create an inline derived table from this table - */ - @PlainSQL override fun where(@Stringly.SQL condition: String, vararg binds: Any?): Message = where(DSL.condition(condition, *binds)) - - /** - * Create an inline derived table from this table - */ - @PlainSQL override fun where(@Stringly.SQL condition: String, vararg parts: QueryPart): Message = where(DSL.condition(condition, *parts)) - - /** - * Create an inline derived table from this table - */ - override fun whereExists(select: Select<*>): Message = where(DSL.exists(select)) - - /** - * Create an inline derived table from this table - */ - override fun whereNotExists(select: Select<*>): Message = where(DSL.notExists(select)) -} diff --git a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/pojos/Message.kt b/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/pojos/Message.kt deleted file mode 100644 index 7976170..0000000 --- a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/pojos/Message.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package at.dokkae.homepage.generated.jooq.tables.pojos - - -import java.io.Serializable -import java.time.OffsetDateTime -import java.util.UUID - - -/** - * This class is generated by jOOQ. - */ -@Suppress("warnings") -data class Message( - val id: UUID, - val author: String, - val content: String, - val createdAt: OffsetDateTime? = null, - val updatedAt: OffsetDateTime? = null -): Serializable { - - override fun equals(other: Any?): Boolean { - if (this === other) - return true - if (other == null) - return false - if (this::class != other::class) - return false - val o: Message = other as Message - if (this.id != o.id) - return false - if (this.author != o.author) - return false - if (this.content != o.content) - return false - if (this.createdAt == null) { - if (o.createdAt != null) - return false - } - else if (this.createdAt != o.createdAt) - return false - if (this.updatedAt == null) { - if (o.updatedAt != null) - return false - } - else if (this.updatedAt != o.updatedAt) - return false - return true - } - - override fun hashCode(): Int { - val prime = 31 - var result = 1 - result = prime * result + this.id.hashCode() - result = prime * result + this.author.hashCode() - result = prime * result + this.content.hashCode() - result = prime * result + (if (this.createdAt == null) 0 else this.createdAt.hashCode()) - result = prime * result + (if (this.updatedAt == null) 0 else this.updatedAt.hashCode()) - return result - } - - override fun toString(): String { - val sb = StringBuilder("Message (") - - sb.append(id) - sb.append(", ").append(author) - sb.append(", ").append(content) - sb.append(", ").append(createdAt) - sb.append(", ").append(updatedAt) - - sb.append(")") - return sb.toString() - } -} diff --git a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/records/MessageRecord.kt b/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/records/MessageRecord.kt deleted file mode 100644 index fb107a6..0000000 --- a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/records/MessageRecord.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package at.dokkae.homepage.generated.jooq.tables.records - - -import at.dokkae.homepage.generated.jooq.tables.Message - -import java.time.OffsetDateTime -import java.util.UUID - -import org.jooq.Record1 -import org.jooq.impl.UpdatableRecordImpl - - -/** - * This class is generated by jOOQ. - */ -@Suppress("warnings") -open class MessageRecord private constructor() : UpdatableRecordImpl(Message.MESSAGE) { - - open var id: UUID - set(value): Unit = set(0, value) - get(): UUID = get(0) as UUID - - open var author: String - set(value): Unit = set(1, value) - get(): String = get(1) as String - - open var content: String - set(value): Unit = set(2, value) - get(): String = get(2) as String - - open var createdAt: OffsetDateTime? - set(value): Unit = set(3, value) - get(): OffsetDateTime? = get(3) as OffsetDateTime? - - open var updatedAt: OffsetDateTime? - set(value): Unit = set(4, value) - get(): OffsetDateTime? = get(4) as OffsetDateTime? - - // ------------------------------------------------------------------------- - // Primary key information - // ------------------------------------------------------------------------- - - override fun key(): Record1 = super.key() as Record1 - - /** - * Create a detached, initialised MessageRecord - */ - constructor(id: UUID, author: String, content: String, createdAt: OffsetDateTime? = null, updatedAt: OffsetDateTime? = null): this() { - this.id = id - this.author = author - this.content = content - this.createdAt = createdAt - this.updatedAt = updatedAt - resetTouchedOnNotNull() - } - - /** - * Create a detached, initialised MessageRecord - */ - constructor(value: at.dokkae.homepage.generated.jooq.tables.pojos.Message?): this() { - if (value != null) { - this.id = value.id - this.author = value.author - this.content = value.content - this.createdAt = value.createdAt - this.updatedAt = value.updatedAt - resetTouchedOnNotNull() - } - } -} diff --git a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/references/Tables.kt b/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/references/Tables.kt deleted file mode 100644 index 4feda24..0000000 --- a/build/generated-src/jooq/at/dokkae/homepage/generated/jooq/tables/references/Tables.kt +++ /dev/null @@ -1,15 +0,0 @@ -@file:Suppress("warnings") -/* - * This file is generated by jOOQ. - */ -package at.dokkae.homepage.generated.jooq.tables.references - - -import at.dokkae.homepage.generated.jooq.tables.Message - - - -/** - * The table public.message. - */ -val MESSAGE: Message = Message.MESSAGE diff --git a/jte-classes/META-INF/main.kotlin_module b/jte-classes/META-INF/main.kotlin_module deleted file mode 100644 index 9dbc290..0000000 Binary files a/jte-classes/META-INF/main.kotlin_module and /dev/null differ diff --git a/jte-classes/gg/jte/generated/ondemand/JteIndexGenerated.kt b/jte-classes/gg/jte/generated/ondemand/JteIndexGenerated.kt deleted file mode 100644 index a762a23..0000000 --- a/jte-classes/gg/jte/generated/ondemand/JteIndexGenerated.kt +++ /dev/null @@ -1,35 +0,0 @@ -@file:Suppress("ktlint") -package gg.jte.generated.ondemand -import at.dokkae.homepage.templates.IndexTemplate -import at.dokkae.homepage.templates.MessageTemplate -import gg.jte.support.ForSupport -@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") -class JteIndexGenerated { -companion object { - @JvmField val JTE_NAME = "Index.kte" - @JvmField val JTE_LINE_INFO = intArrayOf(0,0,0,1,2,4,4,4,4,4,18,18,37,58,88,93,96,96,97,97,98,98,102,108,119,132,146,161,161,161,4,4,4,4,4) - @JvmStatic fun render(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, model:IndexTemplate) { - jteOutput.writeContent("\n\n\n\n \n \n Dokkae's Chat\n\n \n \n \n\n \n\n\n
\n ") - jteOutput.writeContent("\n
\n

Dokkae's Chat

\n
\n\n ") - jteOutput.writeContent("\n
\n
\n ") - for (message in model.messages) { - jteOutput.writeContent("\n ") - gg.jte.generated.ondemand.partials.JteMessageGenerated.render(jteOutput, jteHtmlInterceptor, MessageTemplate(message)); - jteOutput.writeContent("\n ") - } - jteOutput.writeContent("\n
\n
\n\n ") - jteOutput.writeContent("\n
\n
\n ") - jteOutput.writeContent("\n
\n
\n \n
\n
\n\n ") - jteOutput.writeContent("\n
\n
\n
\n \n
\n\n ") - jteOutput.writeContent("\n \n
\n
\n
\n
\n\n ") - jteOutput.writeContent("\n \n
\n\n\n") - } - @JvmStatic fun renderMap(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, params:Map) { - val model = params["model"] as IndexTemplate - render(jteOutput, jteHtmlInterceptor, model); - } -} -} diff --git a/jte-classes/gg/jte/generated/ondemand/partials/JteMessageGenerated.kt b/jte-classes/gg/jte/generated/ondemand/partials/JteMessageGenerated.kt deleted file mode 100644 index 06f028d..0000000 --- a/jte-classes/gg/jte/generated/ondemand/partials/JteMessageGenerated.kt +++ /dev/null @@ -1,46 +0,0 @@ -@file:Suppress("ktlint") -package gg.jte.generated.ondemand.partials -import at.dokkae.homepage.templates.MessageTemplate -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter -import kotlin.math.absoluteValue -@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") -class JteMessageGenerated { -companion object { - @JvmField val JTE_NAME = "partials/Message.kte" - @JvmField val JTE_LINE_INFO = intArrayOf(0,0,0,1,2,3,4,6,6,6,6,6,8,8,8,9,9,10,10,14,15,15,15,15,18,21,21,21,24,24,24,24,24,24,28,30,30,30,34,34,34,6,6,6,6,6) - @JvmStatic fun render(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, model:MessageTemplate) { - jteOutput.writeContent("\n") - val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy").withZone(ZoneId.systemDefault()) - jteOutput.writeContent("\n") - val timeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.systemDefault()) - jteOutput.writeContent("\n") - val borderColors = listOf("red", "orange", "yellow", "green", "blue", "pink" ) - jteOutput.writeContent("\n\n
\n
\n ") - jteOutput.writeContent("\n
\n\n
\n ") - jteOutput.writeContent("\n
\n \n ") - jteOutput.setContext("span", null) - jteOutput.writeUserContent(model.message.author) - jteOutput.writeContent("\n \n \n ") - jteOutput.setContext("span", null) - jteOutput.writeUserContent(dateFormatter.format(model.message.createdAt)) - jteOutput.writeContent(" • ") - jteOutput.setContext("span", null) - jteOutput.writeUserContent(timeFormatter.format(model.message.createdAt)) - jteOutput.writeContent("\n \n
\n\n ") - jteOutput.writeContent("\n
\n ") - jteOutput.setContext("div", null) - jteOutput.writeUserContent(model.message.content) - jteOutput.writeContent("\n
\n
\n
\n
") - } - @JvmStatic fun renderMap(jteOutput:gg.jte.html.HtmlTemplateOutput, jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?, params:Map) { - val model = params["model"] as MessageTemplate - render(jteOutput, jteHtmlInterceptor, model); - } -} -} diff --git a/src/main/kotlin/at/dokkae/homepage/Homepage.kt b/src/main/kotlin/at/dokkae/homepage/Homepage.kt index 636ad47..3f910d9 100644 --- a/src/main/kotlin/at/dokkae/homepage/Homepage.kt +++ b/src/main/kotlin/at/dokkae/homepage/Homepage.kt @@ -1,12 +1,10 @@ package at.dokkae.homepage -import at.dokkae.homepage.config.Env import at.dokkae.homepage.config.Environment import at.dokkae.homepage.extensions.Precompiled import at.dokkae.homepage.repository.MessageRepository import at.dokkae.homepage.repository.impls.JooqMessageRepository -import at.dokkae.homepage.templates.IndexTemplate -import at.dokkae.homepage.templates.MessageTemplate +import at.dokkae.homepage.templates.Index import io.github.cdimascio.dotenv.dotenv import org.flywaydb.core.Flyway import org.http4k.core.HttpHandler @@ -16,19 +14,18 @@ import org.http4k.core.Status import org.http4k.core.body.form import org.http4k.core.getFirst import org.http4k.core.toParametersMap -import org.http4k.routing.ResourceLoader import org.http4k.routing.bindHttp import org.http4k.routing.bindSse import org.http4k.routing.poly import org.http4k.routing.routes import org.http4k.routing.sse -import org.http4k.routing.static import org.http4k.server.Jetty import org.http4k.server.asServer import org.http4k.sse.Sse import org.http4k.sse.SseMessage import org.http4k.sse.SseResponse import org.http4k.template.JTETemplates +import org.http4k.template.ViewModel import org.jooq.SQLDialect import org.jooq.impl.DSL import java.sql.DriverManager @@ -55,11 +52,14 @@ data class Message( 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() { @@ -77,19 +77,10 @@ fun main() { val messageRepository: MessageRepository = JooqMessageRepository(dslContext) val subscribers = CopyOnWriteArrayList() - val renderer = when (env.appEnv) { - Env.DEVELOPMENT -> { - println("🔥 Hot-Reloading JTE templates") - JTETemplates().HotReload("src/main/kte") - } - Env.PRODUCTION -> { - println("📦 Loading pre-compiled JTE templates") - JTETemplates().Precompiled("build/generated-resources/jte") - } - } + val renderer = JTETemplates().Precompiled("build/generated-resources/jte") val indexHandler: HttpHandler = { - Response(Status.OK).body(renderer(IndexTemplate(messageRepository.findAll()))) + Response(Status.OK).body(renderer(Index(messageRepository.findAll()))) } val sse = sse( @@ -103,38 +94,30 @@ fun main() { ) val http = routes( - static(ResourceLoader.Classpath("static")), - "/" bindHttp GET to indexHandler, "/messages" bindHttp POST to { req -> - try { - val params = req.form().toParametersMap() - val author = params.getFirst("author").takeIf { !it.isNullOrBlank() } ?: "Anonymous" - val message = params.getFirst("message") + val params = req.form().toParametersMap() + val author = params.getFirst("author").takeIf { !it.isNullOrBlank() } ?: "Anonymous" + val message = params.getFirst("message") - if (message == null) { - Response(Status.BAD_REQUEST) - } else { - val msg = Message(author, message) - val sseMsg = SseMessage.Data(renderer(MessageTemplate(msg))) + if (message == null) { + Response(Status.BAD_REQUEST) + } else { + val msg = Message(author, message) + val sseMsg = SseMessage.Data(renderer(msg)) - messageRepository.save(msg) - subscribers.forEach { - thread { it.send(sseMsg) } - } - - Response(Status.CREATED) + messageRepository.save(msg) + subscribers.forEach { + thread { it.send(sseMsg) } } - } catch (ex: Exception) { - println("Failed to receive message: ${ex.toString()} ${ex.message}") - Response(Status.INTERNAL_SERVER_ERROR) + Response(Status.CREATED) } } ) - poly(http, sse).asServer(Jetty(port = env.appPort)).start() + poly(http, sse).asServer(Jetty(port = env.port)).start() - println("Server started on http://${env.appDomain}:${env.appPort}") + println("Server started on http://${env.host}:${env.port}") } diff --git a/src/main/kotlin/at/dokkae/homepage/config/Environment.kt b/src/main/kotlin/at/dokkae/homepage/config/Environment.kt index 528c86e..6321095 100644 --- a/src/main/kotlin/at/dokkae/homepage/config/Environment.kt +++ b/src/main/kotlin/at/dokkae/homepage/config/Environment.kt @@ -2,15 +2,9 @@ package at.dokkae.homepage.config import io.github.cdimascio.dotenv.Dotenv -enum class Env { - DEVELOPMENT, - PRODUCTION, -} - data class Environment( - val appPort: Int, - val appDomain: String, - val appEnv: Env, + val port: Int, + val host: String, val dbUrl: String, val dbUsername: String, val dbPassword: String, @@ -22,9 +16,8 @@ data class Environment( * @throws IllegalStateException if required environment variables were not found within the provided `dotenv` instance. */ fun load(dotenv: Dotenv): Environment = Environment( - appPort = requireEnv(dotenv, "APP_PORT").toInt(), - appDomain = requireEnv(dotenv, "APP_DOMAIN"), - appEnv = Env.valueOf(requireEnv(dotenv, "APP_ENV").uppercase()), + port = requireEnv(dotenv, "PORT").toInt(), + host = requireEnv(dotenv, "HOST"), dbUrl = requireEnv(dotenv, "DB_URL"), dbUsername = requireEnv(dotenv, "DB_USERNAME"), dbPassword = requireEnv(dotenv, "DB_PASSWORD"), diff --git a/src/main/kotlin/at/dokkae/homepage/templates/Index.kt b/src/main/kotlin/at/dokkae/homepage/templates/Index.kt index 1e75cb7..1258984 100644 --- a/src/main/kotlin/at/dokkae/homepage/templates/Index.kt +++ b/src/main/kotlin/at/dokkae/homepage/templates/Index.kt @@ -3,6 +3,6 @@ package at.dokkae.homepage.templates import at.dokkae.homepage.Message import org.http4k.template.ViewModel -data class IndexTemplate(val messages: List = listOf()) : ViewModel { +data class Index(val messages: List = listOf()) : ViewModel { override fun template(): String = "Index" } \ No newline at end of file diff --git a/src/main/kotlin/at/dokkae/homepage/templates/Message.kt b/src/main/kotlin/at/dokkae/homepage/templates/Message.kt deleted file mode 100644 index 1b9eb18..0000000 --- a/src/main/kotlin/at/dokkae/homepage/templates/Message.kt +++ /dev/null @@ -1,8 +0,0 @@ -package at.dokkae.homepage.templates - -import at.dokkae.homepage.Message -import org.http4k.template.ViewModel - -data class MessageTemplate(val message: Message) : ViewModel { - override fun template(): String = "partials/Message" -} \ No newline at end of file diff --git a/src/main/kte/Index.kte b/src/main/kte/Index.kte index 7b7933e..08857e8 100644 --- a/src/main/kte/Index.kte +++ b/src/main/kte/Index.kte @@ -1,162 +1,159 @@ -@import at.dokkae.homepage.templates.IndexTemplate -@import at.dokkae.homepage.templates.MessageTemplate -@import gg.jte.support.ForSupport +@import at.dokkae.homepage.templates.Index -@param model: IndexTemplate +@param model: Index - Dokkae's Chat + Simple Chat — http4k + JTE + htmx - - -
- -
-

Dokkae's Chat

-
+ +
+

Simple Chat

- -
-
- @for (message in model.messages) - @template.partials.Message(MessageTemplate(message)) - @endfor -
+
+ @for (message in model.messages.reversed()) + @template.partials.Message(message) + @endfor
- -
-
- -
-
- -
-
- -
-
-
- -
- - - -
-
-
+ + + +
- - +

No auth — anyone can post. Messages are stored only in memory.

- \ No newline at end of file diff --git a/src/main/kte/partials/Message.kte b/src/main/kte/partials/Message.kte index 4e43280..91f406f 100644 --- a/src/main/kte/partials/Message.kte +++ b/src/main/kte/partials/Message.kte @@ -1,35 +1,11 @@ -@import at.dokkae.homepage.templates.MessageTemplate -@import java.time.Instant -@import java.time.ZoneId -@import java.time.format.DateTimeFormatter -@import kotlin.math.absoluteValue +@import at.dokkae.homepage.Message -@param model: MessageTemplate +@param message: Message -!{val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy").withZone(ZoneId.systemDefault())} -!{val timeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.systemDefault())} -!{val borderColors = listOf("red", "orange", "yellow", "green", "blue", "pink" )} - -
-
- -
- -
- -
- - ${model.message.author} - - - ${dateFormatter.format(model.message.createdAt)} • ${timeFormatter.format(model.message.createdAt)} - -
- - -
- ${model.message.content} -
-
-
+
+ ${message.author}: + ${message.content} + + (${message.createdAt.toString()}) +
\ No newline at end of file diff --git a/src/main/resources/db/migration/V003__fix_updated_at_insert_trigger.sql b/src/main/resources/db/migration/V003__fix_updated_at_insert_trigger.sql deleted file mode 100644 index a731f95..0000000 --- a/src/main/resources/db/migration/V003__fix_updated_at_insert_trigger.sql +++ /dev/null @@ -1,35 +0,0 @@ ---- Dropping trigger to prevent them from being called while migrating data -drop trigger if exists handle_message_timestamps on message; - ---- Migrate data -update message -set updated_at = null -where created_at = updated_at - and updated_at is not null; - ---- Replace trigger handler -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 = null; - 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; - ---- Reinsert trigger -create trigger handle_message_timestamps - before insert or update - on message - for each row -execute function handle_timestamps(); \ No newline at end of file diff --git a/src/main/resources/static/css/.keep b/src/main/resources/static/css/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/static/css/index.css b/src/main/resources/static/css/index.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/static/images/.keep b/src/main/resources/static/images/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/static/js/.keep b/src/main/resources/static/js/.keep deleted file mode 100644 index e69de29..0000000