From 7ab48fdbe41bc60e797e2e791d6dc5f766f77105 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Thu, 9 Oct 2025 14:35:50 +0200 Subject: [PATCH 01/20] feat(frontend): Enable frontend router and update config This commit introduces the frontend router and updates the configuration properties. - Adds frontend routing functionality. - Updates default properties for frontend and backend ports. - Integrates the frontend router into the MainVerticle. --- config/default.properties | 8 ++- .../com/ex_dock/ex_dock/MainVerticle.kt | 17 +++++- .../ex_dock/frontend/router/frontendRouter.kt | 57 +++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt diff --git a/config/default.properties b/config/default.properties index 9acc90bf..6fb15eb6 100644 --- a/config/default.properties +++ b/config/default.properties @@ -1,8 +1,14 @@ # Frontend -FRONTEND_PORT=80 +FRONTEND_PORT=8888 BASE_URL=http://127.0.0.1 HOST=127.0.0.1 +# Backend +BACKEND_PORT=9000 + +# Websocket +WEBSOCKET_PORT=9001 + # Database JDBC_URL=127.0.0.1 DATABASE_URL="test_url" diff --git a/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt b/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt index 3b0d1e55..eb6ac11c 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt @@ -6,6 +6,7 @@ import com.ex_dock.ex_dock.frontend.category.router.initCategory import com.ex_dock.ex_dock.frontend.checkout.router.initCheckout import com.ex_dock.ex_dock.frontend.home.router.initHome import com.ex_dock.ex_dock.frontend.product.router.initProduct +import com.ex_dock.ex_dock.frontend.router.enableFrontendRouter import com.ex_dock.ex_dock.frontend.text_pages.router.initTextPages import com.ex_dock.ex_dock.helper.load import com.ex_dock.ex_dock.helper.codecs.registerGenericCodec @@ -49,6 +50,7 @@ class MainVerticle : VerticleBase() { val eventBus = vertx.eventBus() val mainRouter : Router = Router.router(vertx) + val frontendRouter : Router = Router.router(vertx) val store = SessionStore.create(vertx) val sessionHandler = SessionHandler.create(store) @@ -91,17 +93,28 @@ class MainVerticle : VerticleBase() { mainRouter.initCheckout(vertx) mainRouter.initAccount(vertx) + frontendRouter.enableFrontendRouter(vertx, logger) + + vertx.createHttpServer() + .requestHandler(frontendRouter) + .listen(props.getProperty("FRONTEND_PORT").toInt()).onFailure { + logger.error { "Failed to start HTTP server: $it" } + vertx.eventBus().sendError(Exception("Failed to start the HTTP server")) + }.onSuccess { http -> + logger.info { "HTTP server started on port ${props.getProperty("FRONTEND_PORT")}" } + } + return vertx .createHttpServer( HttpServerOptions() .setRegisterWebSocketWriteHandlers(true) ) .requestHandler(mainRouter) - .listen(props.getProperty("FRONTEND_PORT").toInt()).onFailure { error -> + .listen(props.getProperty("BACKEND_PORT").toInt()).onFailure { error -> logger.error { "Failed to start HTTP server: $error" } vertx.eventBus().sendError(Exception("Failed to start the HTTP server")) }.onSuccess { http -> - logger.info { "HTTP server started on port ${props.getProperty("FRONTEND_PORT")}" } + logger.info { "HTTP server started on port ${props.getProperty("BACKEND_PORT")}" } } } } diff --git a/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt b/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt new file mode 100644 index 00000000..1f337e34 --- /dev/null +++ b/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt @@ -0,0 +1,57 @@ +package com.ex_dock.ex_dock.frontend.router + +import com.ex_dock.ex_dock.backend.v1.router.auth.AuthProvider +import com.ex_dock.ex_dock.backend.v1.router.auth.enableAuthRouter +import com.ex_dock.ex_dock.backend.v1.router.docker.initDocker +import com.ex_dock.ex_dock.backend.v1.router.enableBackendV1Router +import com.ex_dock.ex_dock.backend.v1.router.image.initOpenImageRouter +import com.ex_dock.ex_dock.backend.v1.router.websocket.initWebsocket +import com.ex_dock.ex_dock.helper.codecs.registerGenericCodec +import io.github.oshai.kotlinlogging.KLogger +import io.vertx.core.Vertx +import io.vertx.core.eventbus.DeliveryOptions +import io.vertx.core.http.HttpMethod +import io.vertx.ext.auth.PubSecKeyOptions +import io.vertx.ext.auth.jwt.JWTAuth +import io.vertx.ext.auth.jwt.JWTAuthOptions +import io.vertx.ext.web.Router +import io.vertx.ext.web.handler.CorsHandler +import io.vertx.ext.web.handler.JWTAuthHandler + +fun Router.enableFrontendRouter(vertx: Vertx, logger: KLogger) { + val frontendRouter: Router = Router.router(vertx) + val pairDeliveryOptions = DeliveryOptions().setCodecName("PairCodec") + val eventBus = vertx.eventBus() + val authProvider = AuthProvider() + val jwtAuth = JWTAuth.create( + vertx, + JWTAuthOptions() + .addPubSecKey( + PubSecKeyOptions() + .setAlgorithm("RS256") + .setBuffer(authProvider.publicKey) + ) + .addPubSecKey( + PubSecKeyOptions() + .setAlgorithm("RS256") + .setBuffer(authProvider.privateKey) + ) + ) + + eventBus.send( + "process.authentication.registerKeys", + Pair(authProvider.privateKey, authProvider.publicKey), + pairDeliveryOptions + ) + + frontendRouter.route().handler(CorsHandler.create() + .allowedMethod(HttpMethod.OPTIONS) + .allowedMethod(HttpMethod.GET) + ) + + frontendRouter.get("/about").handler { ctx -> + ctx.end("about page for the APIs") + } + + this.route().subRouter(frontendRouter) +} From 1d2d92f3389ce26fdbef4e0b2bc51b86cf04500f Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 10 Oct 2025 10:23:57 +0200 Subject: [PATCH 02/20] feat(url): add URL routing Adds URL management endpoints: - Get, create, update, delete URL keys. - Implements backend V1 router for URL handling. - Refactors URL classes for template/param support. --- .../backend/v1/router/BackendV1Router.kt | 2 + .../backend/v1/router/url/UrlRouter.kt | 65 +++++++++++++++++++ .../ex_dock/database/url/UrlClasses.kt | 52 +++++++-------- .../ex_dock/frontend/router/frontendRouter.kt | 1 + 4 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt diff --git a/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt index 288d3d47..97eefc58 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt @@ -11,6 +11,7 @@ import com.ex_dock.ex_dock.backend.v1.router.sales.initSalesRouter import com.ex_dock.ex_dock.backend.v1.router.pages.enablePagesRouter import com.ex_dock.ex_dock.backend.v1.router.system.enableSystemRouter import com.ex_dock.ex_dock.backend.v1.router.template.initTemplateRouter +import com.ex_dock.ex_dock.backend.v1.router.url.initUrlRouter import com.ex_dock.ex_dock.database.backend_block.BlockInfo import com.ex_dock.ex_dock.database.product.ProductInfo import com.ex_dock.ex_dock.frontend.template_engine.template_data.single_use.SingleUseTemplateData @@ -152,6 +153,7 @@ fun Router.enableBackendV1Router(vertx: Vertx, absoluteMounting: Boolean = false backendV1Router.initProductsRouter(vertx) backendV1Router.initTemplateRouter(vertx) backendV1Router.enablePagesRouter(vertx) + backendV1Router.initUrlRouter(vertx) this.route( diff --git a/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt new file mode 100644 index 00000000..b72975ab --- /dev/null +++ b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt @@ -0,0 +1,65 @@ +package com.ex_dock.ex_dock.backend.v1.router.url + +import com.ex_dock.ex_dock.MainVerticle +import io.vertx.core.Vertx +import io.vertx.core.json.JsonObject +import io.vertx.ext.web.Router + +fun Router.initUrlRouter(vertx: Vertx) { + val urlRouter = Router.router(vertx) + val eventBus = vertx.eventBus() + + urlRouter["/getAll"].handler { ctx -> + eventBus.request>("process.url.getAllUrlKeys", "").onFailure { + ctx.fail(500, it) + }.onSuccess { + ctx.response().putHeader("Content-Type", "application/json") + .end(JsonObject().put("urls", it.body()).encode()) + } + } + + urlRouter["/get/:key"].handler { ctx -> + val key = ctx.pathParam("key") + eventBus.request("process.url.getUrlByKey", key).onFailure { + ctx.fail(500, it) + }.onSuccess { + ctx.response().putHeader("Content-Type", "application/json") + .end(it.body().encode()) + } + } + + urlRouter.post("/create").handler { ctx -> + val body = ctx.body().asJsonObject() + eventBus.request("process.url.createUrlKey", body).onFailure { + ctx.fail(500, it) + MainVerticle.logger.error { it.localizedMessage } + }.onSuccess { + ctx.response().putHeader("Content-Type", "application/json") + .end(it.body().encode()) + } + } + + urlRouter.put("/update").handler { ctx -> + val body = ctx.body().asJsonObject() + eventBus.request("process.url.updateUrlKey", body).onFailure { + ctx.fail(500, it) + MainVerticle.logger.error { it.localizedMessage } + }.onSuccess { + ctx.response().putHeader("Content-Type", "application/json") + .end(it.body().encode()) + } + } + + urlRouter.delete("/delete/:key").handler { ctx -> + val key = ctx.pathParam("key") + eventBus.request("process.url.deleteUrlKey", key).onFailure { + ctx.fail(500, it) + MainVerticle.logger.error { it.localizedMessage } + }.onSuccess { + ctx.end(it.body()) + } + } + + + this.route("/url*").subRouter(urlRouter) +} diff --git a/src/main/kotlin/com/ex_dock/ex_dock/database/url/UrlClasses.kt b/src/main/kotlin/com/ex_dock/ex_dock/database/url/UrlClasses.kt index 11afbd90..3034084f 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/database/url/UrlClasses.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/database/url/UrlClasses.kt @@ -1,54 +1,48 @@ package com.ex_dock.ex_dock.database.url +import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject data class UrlKeys( var urlKey: String?, var upperKey: String, - var requestedId: String, - var pageType: PageType + var templates: List, + var requiredParameters: List ) { companion object { fun fromJson(jsonObject: JsonObject): UrlKeys { + val templates = jsonObject.getJsonArray("templates").map { + it.toString() + } + val requiredParameters = jsonObject.getJsonArray("required_parameters").map { + it.toString() + } + return UrlKeys( urlKey = jsonObject.getString("_id"), upperKey = jsonObject.getString("upper_key"), - requestedId = jsonObject.getString("requested_id"), - pageType = jsonObject.getString("page_type").toPageType() + templates = templates, + requiredParameters = requiredParameters ) } } } fun UrlKeys.toDocument(): JsonObject { + val templates = JsonArray() + this.templates.forEach { template -> + templates.add(template) + } + val requiredParameters = JsonArray() + this.requiredParameters.forEach { requiredParameter -> + requiredParameters.add(requiredParameter) + } + val document = JsonObject() .put("_id", this.urlKey) .put("upper_key", this.upperKey) - .put("requested_id", this.requestedId) - .put("page_type", this.pageType.convertToString()) + .put("templates", templates) + .put("required_parameters", requiredParameters) return document } - -enum class PageType(name: String) { - PRODUCT("product"), - CATEGORY("category"), - TEXT_PAGE("text_page") -} - -fun PageType.convertToString(): String { - return when (this) { - PageType.TEXT_PAGE -> "text_page" - PageType.CATEGORY -> "category" - PageType.PRODUCT -> "product" - } -} - -fun String.toPageType(): PageType { - return when (this) { - "text_page" -> PageType.TEXT_PAGE - "category" -> PageType.CATEGORY - "product" -> PageType.PRODUCT - else -> throw IllegalArgumentException("Invalid page type: $this") - } -} diff --git a/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt b/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt index 1f337e34..ac5cd60a 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt @@ -47,6 +47,7 @@ fun Router.enableFrontendRouter(vertx: Vertx, logger: KLogger) { frontendRouter.route().handler(CorsHandler.create() .allowedMethod(HttpMethod.OPTIONS) .allowedMethod(HttpMethod.GET) + .allowedMethod(HttpMethod.POST) ) frontendRouter.get("/about").handler { ctx -> From b14be917bf03eb5c5e934d02a02bd6246b5bf0f7 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:09:39 +0100 Subject: [PATCH 03/20] fix(template): Enhance template engine caching The template engine caching was improved to enhance performance and prevent stale data. - Refactor cache loading logic - Implement template hit counting - Add list data caching This commit addresses potential issues with template rendering and ensures that the template cache is up-to-date. --- .../template_engine/TemplateEngineVerticle.kt | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/TemplateEngineVerticle.kt b/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/TemplateEngineVerticle.kt index f176b5ec..fa670be9 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/TemplateEngineVerticle.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/TemplateEngineVerticle.kt @@ -17,6 +17,7 @@ import io.pebbletemplates.pebble.template.PebbleTemplate import io.vertx.core.Future import io.vertx.core.VerticleBase import io.vertx.core.eventbus.EventBus +import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject import io.vertx.ext.mongo.MongoClient import java.io.StringWriter @@ -34,6 +35,8 @@ class TemplateEngineVerticle : VerticleBase() { private lateinit var orderCache: AsyncExDockCache private lateinit var shipmentCache: AsyncExDockCache private lateinit var transactionCache: AsyncExDockCache + private lateinit var listCache: AsyncExDockCache + private val engine = PebbleEngine.Builder().loader(StringLoader()).build() private val expireDuration = 10L @@ -50,6 +53,7 @@ class TemplateEngineVerticle : VerticleBase() { orderCache = AsyncExDockCache(vertx) { key -> getOrderCacheData(key) } shipmentCache = AsyncExDockCache(vertx) { key -> getShipmentCacheData(key) } transactionCache = AsyncExDockCache(vertx) { key -> getTransactionCacheData(key) } + listCache = AsyncExDockCache(vertx) { key -> getListCacheData(key) } templateCache = Caffeine.newBuilder() .expireAfterWrite(expireDuration, TimeUnit.MINUTES) @@ -176,7 +180,8 @@ class TemplateEngineVerticle : VerticleBase() { invoiceCache, orderCache, shipmentCache, - transactionCache + transactionCache, + listCache ) val futureMap: MutableMap> = mutableMapOf() @@ -195,6 +200,7 @@ class TemplateEngineVerticle : VerticleBase() { putFuture("order", "order", "orderId") putFuture("shipment", "shipment", "shipmentId") putFuture("transaction", "transaction", "transactionId") + putFuture("list", "list", "listId") val futuresArray = futureMap.values.toTypedArray() return CompletableFuture.allOf(*futuresArray) @@ -272,6 +278,13 @@ class TemplateEngineVerticle : VerticleBase() { ) } + private fun getListCacheData(key: String): CompletableFuture?> { + return getCacheData( + key = key, + eventBusAddress = "process.list.delegateRequest" + ) + } + private fun getCacheData( key: String, deserializer: (JsonObject) -> T, @@ -283,6 +296,17 @@ class TemplateEngineVerticle : VerticleBase() { return future.toCompletionStage().toCompletableFuture() } + + private fun getCacheData( + key: String, + eventBusAddress: String + ): CompletableFuture?> { + val future = eventBus.request(eventBusAddress, key) + .map { CacheData(it.body(), 0) } + .otherwise { null } + + return future.toCompletionStage().toCompletableFuture() + } } private data class TemplateCacheData( From 4c01441613224dd7a0d1ded3e27207a0edb73b5c Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:48:42 +0100 Subject: [PATCH 04/20] fix(auth): Fix package declaration The package declaration in `AuthenticationVerticle.kt` was corrected to adhere to standard conventions. This change resolves a minor formatting issue and improves code consistency. * Corrected the package declaration --- .../ex_dock/ex_dock/database/auth/AuthenticationVerticle.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/database/auth/AuthenticationVerticle.kt b/src/main/kotlin/com/ex_dock/ex_dock/database/auth/AuthenticationVerticle.kt index 78867932..79ba703e 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/database/auth/AuthenticationVerticle.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/database/auth/AuthenticationVerticle.kt @@ -119,7 +119,7 @@ class AuthenticationVerticle : VerticleBase() { val newTokenOptions = JWTOptions() .setAlgorithm("RS256") .setExpiresInMinutes(15) - .setSubject(convertedUser.principal().getString("sub")) + .setSubject(convertedUser.principal().getString("id")) .setIssuer(issuer) val newClaims = JsonObject() .put("permissions", permissions) @@ -128,7 +128,7 @@ class AuthenticationVerticle : VerticleBase() { val refreshTokenOptions = JWTOptions() .setAlgorithm("RS256") .setExpiresInMinutes(60 * 24 * 7) - .setSubject(user.principal().getString("id")) + .setSubject(user.principal().getString("sub")) .setIssuer(issuer) val refreshClaims = JsonObject() refreshClaims.put("jti", "jti-" + UUID.randomUUID().toString()) From 53a55cd6c8265a399b906ed141b55f43759c7e22 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:48:48 +0100 Subject: [PATCH 05/20] refactor(backend): Refactor BackendRouter - Update imports and auth handling. - Use authProvider for JWT creation. - Register codec for Pair. --- src/main/kotlin/com/ex_dock/ex_dock/backend/BackendRouter.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/backend/BackendRouter.kt b/src/main/kotlin/com/ex_dock/ex_dock/backend/BackendRouter.kt index a2d0593a..da4159e7 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/backend/BackendRouter.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/backend/BackendRouter.kt @@ -18,11 +18,10 @@ import io.vertx.ext.web.Router import io.vertx.ext.web.handler.CorsHandler import io.vertx.ext.web.handler.JWTAuthHandler -fun Router.enableBackendRouter(vertx: Vertx, logger: KLogger) { +fun Router.enableBackendRouter(vertx: Vertx, logger: KLogger, authProvider: AuthProvider) { val backendRouter: Router = Router.router(vertx) val pairDeliveryOptions = DeliveryOptions().setCodecName("PairCodec") val eventBus = vertx.eventBus() - val authProvider = AuthProvider() val jwtAuth = JWTAuth.create( vertx, JWTAuthOptions() From 25b08bbc316e9f89f2a5648e82ea4fe2944fc1e1 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:48:54 +0100 Subject: [PATCH 06/20] fix(router): Correct package and import statements The commit corrects package declarations and import statements in `BackendV1Router.kt`. - Fixes inconsistent newline formatting. - Updates package names and import statements to align with project structure. This ensures proper module resolution. --- .../com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt index 97eefc58..d32a05de 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/BackendV1Router.kt @@ -33,7 +33,6 @@ fun Router.enableBackendV1Router(vertx: Vertx, absoluteMounting: Boolean = false val listDeliveryOptions = DeliveryOptions().setCodecName("ListCodec") val backendV1Router: Router = Router.router(vertx) val eventBus: EventBus = vertx.eventBus() - val authProvider = AuthProvider() val exDockAuthHandler = ExDockAuthHandler(vertx) backendV1Router.route().handler(BodyHandler.create()) @@ -172,6 +171,8 @@ private fun JsonArray.convertAddresses(): JsonArray { "category" -> resultArray.add(Pair("process.category.getCategoryById", address.getString("id"))) "template" -> resultArray.add(Pair("process.template.getTemplateByKey", address.getString("id"))) "templatesAll" -> resultArray.add(Pair("process.template.getAllTemplates", address.getString("id"))) + "url" -> resultArray.add(Pair("process.url.getUrlByKey", address.getString("id"))) + "urlsAll" -> resultArray.add(Pair("process.url.getAllUrlKeys", address.getString("id"))) else -> MainVerticle.logger.info { "Unknown address: $address" } } } From bbcd203639e6b20e12a4c5d7fddd292406dc428b Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:49:00 +0100 Subject: [PATCH 07/20] fix(data): Correct data accessor imports The import statements in `DataAccessor.kt` were reordered and a `listCache` property of type AsyncExDockCache was added. This change ensures correct dependency resolution and enables caching of JSON arrays. - Reordered existing import statements. - Added `listCache` property for JsonArray caching. - Updated get() method to handle "list" type. --- .../ex_dock/ex_dock/frontend/template_engine/DataAccessor.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/DataAccessor.kt b/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/DataAccessor.kt index 25d5fdaa..c6e92d7d 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/DataAccessor.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/DataAccessor.kt @@ -9,6 +9,7 @@ import com.ex_dock.ex_dock.database.sales.Shipment import com.ex_dock.ex_dock.database.sales.Transaction import com.ex_dock.ex_dock.helper.AsyncExDockCache import com.ex_dock.ex_dock.helper.ExDockCache +import io.vertx.core.json.JsonArray import java.util.concurrent.CompletableFuture class DataAccessor( @@ -19,6 +20,7 @@ class DataAccessor( private val orderCache: AsyncExDockCache, private val shipmentCache: AsyncExDockCache, private val transactionCache: AsyncExDockCache, + private val listCache: AsyncExDockCache, ) { fun get(type: String, key: String): CompletableFuture<*>? { @@ -30,6 +32,7 @@ class DataAccessor( "order" -> orderCache.getById(key) "shipment" -> shipmentCache.getById(key) "transaction" -> transactionCache.getById(key) + "list" -> listCache.getById(key) else -> null } } From 40f0607bf657d7ef585ee8db9ee6f8e7d30c4652 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:49:06 +0100 Subject: [PATCH 08/20] fix(frontend): Improve page rendering - Fetch and render dynamic content - Handle missing URL parameters - Fix error handling for URL requests --- .../ex_dock/frontend/router/frontendRouter.kt | 86 ++++++++++++------- 1 file changed, 55 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt b/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt index ac5cd60a..c21d17f4 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt @@ -1,42 +1,20 @@ package com.ex_dock.ex_dock.frontend.router import com.ex_dock.ex_dock.backend.v1.router.auth.AuthProvider -import com.ex_dock.ex_dock.backend.v1.router.auth.enableAuthRouter -import com.ex_dock.ex_dock.backend.v1.router.docker.initDocker -import com.ex_dock.ex_dock.backend.v1.router.enableBackendV1Router -import com.ex_dock.ex_dock.backend.v1.router.image.initOpenImageRouter -import com.ex_dock.ex_dock.backend.v1.router.websocket.initWebsocket -import com.ex_dock.ex_dock.helper.codecs.registerGenericCodec +import com.ex_dock.ex_dock.database.url.UrlKeys import io.github.oshai.kotlinlogging.KLogger +import io.vertx.core.Future import io.vertx.core.Vertx import io.vertx.core.eventbus.DeliveryOptions import io.vertx.core.http.HttpMethod -import io.vertx.ext.auth.PubSecKeyOptions -import io.vertx.ext.auth.jwt.JWTAuth -import io.vertx.ext.auth.jwt.JWTAuthOptions +import io.vertx.core.json.JsonObject import io.vertx.ext.web.Router import io.vertx.ext.web.handler.CorsHandler -import io.vertx.ext.web.handler.JWTAuthHandler -fun Router.enableFrontendRouter(vertx: Vertx, logger: KLogger) { +fun Router.enableFrontendRouter(vertx: Vertx, logger: KLogger, authProvider: AuthProvider) { val frontendRouter: Router = Router.router(vertx) val pairDeliveryOptions = DeliveryOptions().setCodecName("PairCodec") val eventBus = vertx.eventBus() - val authProvider = AuthProvider() - val jwtAuth = JWTAuth.create( - vertx, - JWTAuthOptions() - .addPubSecKey( - PubSecKeyOptions() - .setAlgorithm("RS256") - .setBuffer(authProvider.publicKey) - ) - .addPubSecKey( - PubSecKeyOptions() - .setAlgorithm("RS256") - .setBuffer(authProvider.privateKey) - ) - ) eventBus.send( "process.authentication.registerKeys", @@ -44,14 +22,60 @@ fun Router.enableFrontendRouter(vertx: Vertx, logger: KLogger) { pairDeliveryOptions ) - frontendRouter.route().handler(CorsHandler.create() - .allowedMethod(HttpMethod.OPTIONS) - .allowedMethod(HttpMethod.GET) - .allowedMethod(HttpMethod.POST) + frontendRouter.route().handler( + CorsHandler.create() + .allowedMethod(HttpMethod.OPTIONS) + .allowedMethod(HttpMethod.GET) + .allowedMethod(HttpMethod.POST) ) frontendRouter.get("/about").handler { ctx -> - ctx.end("about page for the APIs") + ctx.end("about page for the Frontend") + } + + frontendRouter["/*"].handler { ctx -> + val params = ctx.queryParams() + val fullUrl = ctx.request().path() + + eventBus.request("process.url.getUrlByKey", fullUrl).onFailure { + ctx.fail(404, Error("Page not found")) + return@onFailure + }.onSuccess { fetchedUrl -> + val urlObject: UrlKeys = UrlKeys.fromJson(fetchedUrl.body()) + val request = JsonObject() + + for (param in urlObject.requiredParameters) { + request.put(param, params.get(param)) + if (!params.contains(param)) { + ctx.fail(400, Error("Missing required parameter: $param")) + return@onSuccess + } + } + + val futures = mutableListOf>() + var pageCode = "" + urlObject.templates.forEach { templateId -> + futures.add( + Future.future { promise -> + val newBody = JsonObject(request.toString()).put("template_key", templateId) + eventBus.request("template.generate.compiled", newBody).onFailure { + promise.fail(it.message) + }.onSuccess { + pageCode += it.body() + promise.complete() + } + } + ) + } + + Future.all(futures).onFailure { + ctx.fail(500, it) + }.onSuccess { _ -> + ctx.response() + .putHeader("Content-Type", "text/html") + .end(pageCode) + } + } } this.route().subRouter(frontendRouter) From 4a491fc739348a8eb5bfddcec1637aff115cf226 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:49:12 +0100 Subject: [PATCH 09/20] fix(jdbcHelper): Fix error handling in JDBC queries The change improves error handling for JDBC queries. - Replaces generic error message with JSON object containing error details. - Ensures errors are properly logged. This prevents issues when a JDBC query returns no results. --- src/main/kotlin/com/ex_dock/ex_dock/helper/jdbcHelper.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/helper/jdbcHelper.kt b/src/main/kotlin/com/ex_dock/ex_dock/helper/jdbcHelper.kt index 95f5b73c..4fb59d48 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/helper/jdbcHelper.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/helper/jdbcHelper.kt @@ -28,7 +28,8 @@ fun Future>.replySingleMessage(message: Message) { this.onSuccess { res -> if (res.isEmpty()) { MainVerticle.logger.error { "Requested JDBC query did not return an entry" } - message.fail(500, "Requested JDBC query did not return an entry") + message.fail(500, JsonObject() + .put("error", "Requested JDBC query did not return an entry").encode()) return@onSuccess } message.reply(res.first()) From 76066a4cfca139fbd614b212802df69e96b368ea Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:49:16 +0100 Subject: [PATCH 10/20] fix(core): Fix JDBCStarter imports The JDBCStarter.kt file had incorrect import statements. - Corrected package imports. - Removed unnecessary blank lines. This ensures the correct dependencies are resolved. --- src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt b/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt index 5147bade..fa9d3eab 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt @@ -15,6 +15,7 @@ import com.ex_dock.ex_dock.database.checkout.CheckoutJdbcVerticle import com.ex_dock.ex_dock.database.home.HomeJdbcVerticle import com.ex_dock.ex_dock.database.image.Image import com.ex_dock.ex_dock.database.image.ImageProduct +import com.ex_dock.ex_dock.database.list.ListVerticle import com.ex_dock.ex_dock.database.product.ProductInfo import com.ex_dock.ex_dock.database.product.ProductJdbcVerticle import com.ex_dock.ex_dock.database.sales.* @@ -28,6 +29,7 @@ import com.ex_dock.ex_dock.database.template.TemplateJdbcVerticle import com.ex_dock.ex_dock.database.text_pages.TextPages import com.ex_dock.ex_dock.database.text_pages.TextPagesJdbcVerticle import com.ex_dock.ex_dock.database.url.UrlJdbcVerticle +import com.ex_dock.ex_dock.database.url.UrlKeys import com.ex_dock.ex_dock.frontend.cache.CacheVerticle import com.ex_dock.ex_dock.frontend.template_engine.TemplateEngineVerticle import com.ex_dock.ex_dock.frontend.template_engine.template_data.single_use.SingleUseTemplateData @@ -99,6 +101,7 @@ class JDBCStarter : VerticleBase() { verticles.add(vertx.deployWorkerVerticleHelper(TemplateEngineVerticle::class)) verticles.add(vertx.deployWorkerVerticleHelper(SalesJdbcVerticle::class)) verticles.add(vertx.deployWorkerVerticleHelper(TemplateJdbcVerticle::class)) + verticles.add(vertx.deployWorkerVerticleHelper(ListVerticle::class)) verticles.add(vertx.deployWorkerVerticleHelper(TemplateEngineVerticle::class, workerPoolSize = 5, poolName = "template-cache-isolation-pool")) } @@ -124,6 +127,7 @@ class JDBCStarter : VerticleBase() { .registerGenericCodec(List::class) .registerGenericCodec(SingleUseTemplateData::class) .registerGenericCodec(ArrayList::class) + .registerGenericCodec(UrlKeys::class) .registerGenericListCodec(FullUser::class) .registerGenericListCodec(JsonObject::class) From 5a0bb7846a160c627921d647fb5c2e4ed7f737eb Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:49:52 +0100 Subject: [PATCH 11/20] fix(json): Fix JsonElement extension functions The JsonElement extension functions contained extraneous newline and carriage return characters causing issues. - Remove unnecessary newline characters - Remove unnecessary carriage return characters --- src/main/kotlin/com/ex_dock/ex_dock/helper/JsonHelper.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/helper/JsonHelper.kt b/src/main/kotlin/com/ex_dock/ex_dock/helper/JsonHelper.kt index 3532a548..617342e2 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/helper/JsonHelper.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/helper/JsonHelper.kt @@ -24,6 +24,7 @@ fun JsonElement.findValueByFieldName(fieldName: String): JsonElement? { if (foundValue != null) return foundValue } } + else -> return null } return null // Not found } From 76bde22ba418c3ef6de24a2125646d6f58641c38 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:49:57 +0100 Subject: [PATCH 12/20] feat(database): add ListVerticle for data delegation Adds ListVerticle for delegating list requests. - Implements event bus communication. - Delegates requests based on list name. - Handles successful and failed requests. --- .../ex_dock/database/list/ListVerticle.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/kotlin/com/ex_dock/ex_dock/database/list/ListVerticle.kt diff --git a/src/main/kotlin/com/ex_dock/ex_dock/database/list/ListVerticle.kt b/src/main/kotlin/com/ex_dock/ex_dock/database/list/ListVerticle.kt new file mode 100644 index 00000000..4476931e --- /dev/null +++ b/src/main/kotlin/com/ex_dock/ex_dock/database/list/ListVerticle.kt @@ -0,0 +1,46 @@ +package com.ex_dock.ex_dock.database.list + +import io.vertx.core.Future +import io.vertx.core.VerticleBase +import io.vertx.core.eventbus.EventBus +import io.vertx.core.json.JsonArray +import io.vertx.core.json.JsonObject + +class ListVerticle : VerticleBase() { + private lateinit var eventBus: EventBus + private val addressDictionary = mutableMapOf( + Pair("product", "process.product.getAllProducts"), + Pair("category", "process.category.getAllCategories"), + Pair("credit_memo", "process.sales.getAllCreditMemos"), + Pair("invoice", "process.sales.getAllInvoices"), + Pair("order", "process.sales.getAllOrders"), + Pair("shipment", "process.sales.getAllShipments"), + Pair("transaction", "process.sales.getAllTransactions"), + ) + + override fun start(): Future<*>? { + eventBus = vertx.eventBus() + + delegateListRequest() + + return super.start(); + } + + private fun delegateListRequest() { + eventBus.consumer("process.list.delegateRequest") { message -> + val listName = message.body() + + if (addressDictionary.containsKey(listName)) { + val address = addressDictionary[listName] + eventBus.request< List>(address, "").onFailure { + message.fail(500, it.localizedMessage) + }.onSuccess { + val reply = it.body() + val jsonArray = JsonArray(reply) + message.reply(jsonArray) + } + } + } + } + +} From 3cabdcdde033348f03308f9c46c040b4813a733d Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:50:02 +0100 Subject: [PATCH 13/20] fix(core): Refactor MainVerticle dependencies The MainVerticle class was updated to: - Update dependencies and imports for backend and frontend routers. - Add websocket router. - Update server ports configuration. --- .../com/ex_dock/ex_dock/MainVerticle.kt | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt b/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt index eb6ac11c..2afb89f0 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt @@ -1,6 +1,7 @@ package com.ex_dock.ex_dock import com.ex_dock.ex_dock.backend.enableBackendRouter +import com.ex_dock.ex_dock.backend.v1.router.auth.AuthProvider import com.ex_dock.ex_dock.frontend.account.router.initAccount import com.ex_dock.ex_dock.frontend.category.router.initCategory import com.ex_dock.ex_dock.frontend.checkout.router.initCheckout @@ -11,6 +12,7 @@ import com.ex_dock.ex_dock.frontend.text_pages.router.initTextPages import com.ex_dock.ex_dock.helper.load import com.ex_dock.ex_dock.helper.codecs.registerGenericCodec import com.ex_dock.ex_dock.helper.sendError +import com.ex_dock.ex_dock.websocket.router.enableWebsocketRouter import io.github.oshai.kotlinlogging.KotlinLogging import io.vertx.core.Future import io.vertx.core.VerticleBase @@ -51,10 +53,12 @@ class MainVerticle : VerticleBase() { val eventBus = vertx.eventBus() val mainRouter : Router = Router.router(vertx) val frontendRouter : Router = Router.router(vertx) + val websocketRouter : Router = Router.router(vertx) val store = SessionStore.create(vertx) val sessionHandler = SessionHandler.create(store) + val authProvider = AuthProvider() - eventBus.registerGenericCodec(List::class) +// eventBus.registerGenericCodec(List::class) eventBus.consumer>("process.main.registerVerticleId").handler { message -> val verticleIds: List = message.body() verticleIds.forEach { value -> @@ -84,7 +88,7 @@ class MainVerticle : VerticleBase() { mainRouter.route().handler(sessionHandler) mainRouter.route().handler(BodyHandler.create()) - mainRouter.enableBackendRouter(vertx, logger) + mainRouter.enableBackendRouter(vertx, logger, authProvider) mainRouter.initHome(eventBus) mainRouter.initProduct(vertx) @@ -93,7 +97,8 @@ class MainVerticle : VerticleBase() { mainRouter.initCheckout(vertx) mainRouter.initAccount(vertx) - frontendRouter.enableFrontendRouter(vertx, logger) + frontendRouter.enableFrontendRouter(vertx, logger, authProvider) + websocketRouter.enableWebsocketRouter(vertx, logger) vertx.createHttpServer() .requestHandler(frontendRouter) @@ -104,6 +109,18 @@ class MainVerticle : VerticleBase() { logger.info { "HTTP server started on port ${props.getProperty("FRONTEND_PORT")}" } } + vertx.createHttpServer( + HttpServerOptions() + .setRegisterWebSocketWriteHandlers(true) + ) + .requestHandler(websocketRouter) + .listen(props.getProperty("WEBSOCKET_PORT").toInt()).onFailure { + logger.error { "Failed to start HTTP server: $it" } + vertx.eventBus().sendError(Exception("Failed to start the HTTP server")) + }.onSuccess { _ -> + logger.info { "HTTP server started on port ${props.getProperty("WEBSOCKET_PORT")}" } + } + return vertx .createHttpServer( HttpServerOptions() From 13bf0ed29fb59e170e254366f9c1c25a86bd03aa Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:50:08 +0100 Subject: [PATCH 14/20] feat(websocket): Add websocket page router Adds a websocket endpoint for page preview. - Implements a websocket router for handling page preview requests. - Authenticates websocket connections using handleWebSocketAuthentication. - Generates page code from templates and sends it to connected clients. --- .../ex_dock/websocket/page/pageRouter.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/main/kotlin/com/ex_dock/ex_dock/websocket/page/pageRouter.kt diff --git a/src/main/kotlin/com/ex_dock/ex_dock/websocket/page/pageRouter.kt b/src/main/kotlin/com/ex_dock/ex_dock/websocket/page/pageRouter.kt new file mode 100644 index 00000000..cd609041 --- /dev/null +++ b/src/main/kotlin/com/ex_dock/ex_dock/websocket/page/pageRouter.kt @@ -0,0 +1,76 @@ +package com.ex_dock.ex_dock.websocket.page + +import com.ex_dock.ex_dock.MainVerticle +import com.ex_dock.ex_dock.helper.handleWebSocketAuthentication +import io.github.oshai.kotlinlogging.KLogger +import io.vertx.core.Future +import io.vertx.core.Vertx +import io.vertx.core.buffer.Buffer +import io.vertx.core.http.ServerWebSocket +import io.vertx.core.json.JsonObject +import io.vertx.ext.web.Router +import java.util.concurrent.ConcurrentHashMap + +fun Router.enableWebsocketPageRouter(vertx: Vertx, logger: KLogger) { + val websocketPageRouter = Router.router(vertx) + val connectedClients = ConcurrentHashMap() + val userIdToConnectionId = ConcurrentHashMap() + val clientPools = ConcurrentHashMap>() + val eventBus = vertx.eventBus() + + websocketPageRouter["/preview"].handler { ctx -> + ctx.request().toWebSocket().onSuccess { result -> + vertx.handleWebSocketAuthentication( + result = result, + logger = logger, + connectedClients = connectedClients, + userIdToConnectionId = userIdToConnectionId, + clientPools = clientPools, + ) { authenticatedUserId, buffer -> + val websocket = connectedClients[authenticatedUserId] + val message = buffer.toString() + val futures = mutableListOf>() + val body = JsonObject(message) + val templateIds = body.getJsonArray("templateIds") + var pageCode = "" + + templateIds.forEach { templateId -> + futures.add( + Future.future { promise -> + eventBus.request("process.template.getTemplateByKey", templateId.toString()).onFailure { + promise.fail(it.message) + }.onSuccess { + pageCode += it.body().getString("template_data") + promise.complete() + } + } + ) + } + + Future.all(futures).onFailure { + websocket?.writeTextMessage(it.message) + }.onSuccess { _ -> + body.put("templateData", pageCode) + body.remove("templateIds") + + eventBus.request("template.generate.singleUse", body).onFailure { + MainVerticle.logger.error { it.localizedMessage } + websocket?.writeTextMessage(it.message) + }.onSuccess { + for (ws in clientPools[authenticatedUserId]!!) { + ws.writeTextMessage(JsonObject() + .put("type", "pagePreview") + .put("previewCode", it.body()) + .encode()) + } + } + } + } + }.onFailure { _ -> + logger.error { "Failed to upgrade to WebSocket" } + ctx.response().setStatusCode(400).end("Failed to upgrade to WebSocket") + } + } + + this.route("/page*").subRouter(websocketPageRouter) +} From 2eb74f04da85c551093f9025c8a1ed3e6acfb5e6 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:50:14 +0100 Subject: [PATCH 15/20] refactor(template): Refactor template cache The template cache was refactored to use AsyncExDockCache. - Replaced Caffeine cache with AsyncExDockCache - Modified template processing to leverage new cache - Removed hit counter logic This change improves cache management and simplifies the TemplateEngineVerticle. --- .../template_engine/TemplateEngineVerticle.kt | 86 ++++++------------- 1 file changed, 25 insertions(+), 61 deletions(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/TemplateEngineVerticle.kt b/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/TemplateEngineVerticle.kt index fa670be9..91a85c91 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/TemplateEngineVerticle.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/frontend/template_engine/TemplateEngineVerticle.kt @@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit class TemplateEngineVerticle : VerticleBase() { private lateinit var client: MongoClient private lateinit var eventBus: EventBus - private lateinit var templateCache: LoadingCache + private lateinit var templateCache: AsyncExDockCache private lateinit var productCache: AsyncExDockCache private lateinit var categoryCache: AsyncExDockCache private lateinit var creditMemoCache: AsyncExDockCache @@ -46,6 +46,7 @@ class TemplateEngineVerticle : VerticleBase() { override fun start(): Future<*>? { client = vertx.getConnection() eventBus = vertx.eventBus() + templateCache = AsyncExDockCache(vertx) { key -> getTemplateCacheData(key) } productCache = AsyncExDockCache(vertx) { key -> getProductCacheData(key) } categoryCache = AsyncExDockCache(vertx) { key -> getCategoryCacheData(key) } creditMemoCache = AsyncExDockCache(vertx) { key -> getCreditMemoCacheData(key) } @@ -55,33 +56,8 @@ class TemplateEngineVerticle : VerticleBase() { transactionCache = AsyncExDockCache(vertx) { key -> getTransactionCacheData(key) } listCache = AsyncExDockCache(vertx) { key -> getListCacheData(key) } - templateCache = Caffeine.newBuilder() - .expireAfterWrite(expireDuration, TimeUnit.MINUTES) - .refreshAfterWrite(refreshDuration, TimeUnit.MINUTES) - .build(CacheLoader { key -> - val future = eventBus.request("process.template.getTemplateByKey", key) - .map { message -> - TemplateCacheData( - engine.getTemplate(message.body().getString("template_data")), - 0 - ) - }.otherwise { null } - - val javaFuture = future - .toCompletionStage() - .toCompletableFuture() - - return@CacheLoader try { - javaFuture.join() - } catch (e: Exception) { - MainVerticle.logger.error { "Cache loader failed for key: $key\n${e.localizedMessage}" } - null - } - }) - singleUseTemplate() getCompiledTemplate() - invalidateCacheKey() return Future.succeededFuture() } @@ -123,12 +99,20 @@ class TemplateEngineVerticle : VerticleBase() { vertx.executeBlocking({ try { val key = body.getString("template_key") - incrementTemplateHitCount(key) - val template = templateCache.get(key) - - val writer = StringWriter() - template.templateData.evaluate(writer, context) - return@executeBlocking writer.toString() + val template = templateCache.getById(key) + + template.whenComplete { temp, err -> + if (err != null || temp == null) { + MainVerticle.logger.error { err.localizedMessage } + return@whenComplete + } + + val writer = StringWriter() + temp.data.templateData.evaluate(writer, context) + message.reply(writer.toString()) + return@whenComplete + } + return@executeBlocking } catch (e: Exception) { MainVerticle.logger.error { e.localizedMessage } throw e @@ -142,35 +126,6 @@ class TemplateEngineVerticle : VerticleBase() { } } - private fun incrementTemplateHitCount(key: String) { - val templateCacheData = templateCache.getIfPresent(key) - - // Check if the cache data exists and is not expired or deleted - if (templateCacheData != null) { - - // Check if the template data hits exceed the maximum hits or if the flag is set - if (templateCacheData.hits >= maxHitCount) { - templateCache.invalidate(key) - println("CACHE DATA EXPIRED") - return - } - - templateCacheData.hits++ - templateCache.put(key, templateCacheData) - } - } - - private fun invalidateCacheKey() { - eventBus.consumer("template.cache.invalidate") { _ -> - val keys = templateCache.asMap().keys - - for (key in keys) { - templateCache.refresh(key) - println("CACHE DATA REFRESHED FOR KEY: $key") - } - } - } - private fun getContextData(ids: JsonObject): CompletableFuture> { val context: MutableMap = mutableMapOf() val accessor = DataAccessor( @@ -222,6 +177,15 @@ class TemplateEngineVerticle : VerticleBase() { } } + private fun getTemplateCacheData(key: String): CompletableFuture?> { + return getCacheData( + key = key, + deserializer = { TemplateCacheData(engine.getTemplate(it.getString("template_data")), 0) + }, + eventBusAddress = "process.template.getTemplateByKey" + ) + } + private fun getProductCacheData(key: String): CompletableFuture?> { return getCacheData( key = key, From 163fcc2868665ae2fa6a287936d51f048cae7743 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:50:20 +0100 Subject: [PATCH 16/20] fix(url): Refactor URL creation and update The request body was not correctly parsed when creating/updating URLs, causing errors. - Update URL creation/update endpoints to correctly parse the request body using UrlKeys.fromJson(). - Use UrlKeys codec for event bus communication. - Return updated UrlKeys object as document --- .../ex_dock/backend/v1/router/url/UrlRouter.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt index b72975ab..23764475 100644 --- a/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt +++ b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt @@ -1,13 +1,18 @@ package com.ex_dock.ex_dock.backend.v1.router.url import com.ex_dock.ex_dock.MainVerticle +import com.ex_dock.ex_dock.database.url.UrlKeys +import com.ex_dock.ex_dock.database.url.toDocument import io.vertx.core.Vertx +import io.vertx.core.eventbus.DeliveryOptions import io.vertx.core.json.JsonObject import io.vertx.ext.web.Router fun Router.initUrlRouter(vertx: Vertx) { val urlRouter = Router.router(vertx) val eventBus = vertx.eventBus() + val urlDeliveryOptions = DeliveryOptions().setCodecName("UrlKeysCodec") + urlRouter["/getAll"].handler { ctx -> eventBus.request>("process.url.getAllUrlKeys", "").onFailure { @@ -29,24 +34,25 @@ fun Router.initUrlRouter(vertx: Vertx) { } urlRouter.post("/create").handler { ctx -> - val body = ctx.body().asJsonObject() - eventBus.request("process.url.createUrlKey", body).onFailure { + val requestBody = ctx.body().asJsonObject() + val body = UrlKeys.fromJson(requestBody) + eventBus.request("process.url.createUrlKey", body, urlDeliveryOptions).onFailure { ctx.fail(500, it) MainVerticle.logger.error { it.localizedMessage } }.onSuccess { ctx.response().putHeader("Content-Type", "application/json") - .end(it.body().encode()) + .end(it.body().toDocument().encode()) } } urlRouter.put("/update").handler { ctx -> - val body = ctx.body().asJsonObject() - eventBus.request("process.url.updateUrlKey", body).onFailure { + val body = UrlKeys.fromJson(ctx.body().asJsonObject()) + eventBus.request("process.url.updateUrlKey", body).onFailure { ctx.fail(500, it) MainVerticle.logger.error { it.localizedMessage } }.onSuccess { ctx.response().putHeader("Content-Type", "application/json") - .end(it.body().encode()) + .end(it.body().toDocument().encode()) } } From dda312158ab61c0bbd20e80260780cd20c90e4ce Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:50:26 +0100 Subject: [PATCH 17/20] feat(websocket): add WebsocketHelper for auth This commit introduces `WebsocketHelper.kt` to handle websocket authentication. - Implements authentication logic. - Manages client connections and pools. - Handles message processing after authentication. --- .../ex_dock/ex_dock/helper/WebsocketHelper.kt | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/main/kotlin/com/ex_dock/ex_dock/helper/WebsocketHelper.kt diff --git a/src/main/kotlin/com/ex_dock/ex_dock/helper/WebsocketHelper.kt b/src/main/kotlin/com/ex_dock/ex_dock/helper/WebsocketHelper.kt new file mode 100644 index 00000000..a8cc0da4 --- /dev/null +++ b/src/main/kotlin/com/ex_dock/ex_dock/helper/WebsocketHelper.kt @@ -0,0 +1,120 @@ +package com.ex_dock.ex_dock.helper + +import com.ex_dock.ex_dock.backend.v1.router.websocket.setAuthTimer +import io.github.oshai.kotlinlogging.KLogger +import io.vertx.core.Vertx +import io.vertx.core.buffer.Buffer +import io.vertx.core.http.ServerWebSocket +import io.vertx.core.json.JsonObject +import kotlin.collections.set +import kotlin.properties.Delegates + +fun Vertx.handleWebSocketAuthentication( + result: ServerWebSocket, + logger: KLogger, + authTimeOutMillis: Long = 10000L, + connectedClients: MutableMap, + userIdToConnectionId: MutableMap, + clientPools: MutableMap>? = null, + mainMessageHandler: (String, Buffer) -> Unit + ) { + val webSocket = result + val clientId = webSocket.binaryHandlerID() + var authenticatedUserId: String? = null + var timerId by Delegates.notNull() + var firstAuthAttempt = true + + logger.info { "Client $clientId attempting to connect..." } + + timerId = this.setAuthTimer(authTimeOutMillis, authenticatedUserId, webSocket) + + webSocket.handler { accessBuffer -> + this.cancelTimer(timerId) + + try { + val authMessageJson = accessBuffer.toJsonObject() + val accessToken = authMessageJson.getString("token") + + this.eventBus().request( + "process.authentication.authenticateToken", accessToken).onComplete { result -> + if (result.succeeded()) { + val userIdFromAuth = result.result().body() + authenticatedUserId = userIdFromAuth + + userIdToConnectionId[userIdFromAuth] = clientId + connectedClients[userIdFromAuth] = webSocket + + logger.info { "Client $clientId authenticated successfully as user $authenticatedUserId." } + webSocket.writeTextMessage( + JsonObject() + .put("type", "auth_success") + .put("message", "Authentication successful.") + .encode() + ) + + val authenticatedHandler: (Buffer) -> Unit = { buffer -> + mainMessageHandler(authenticatedUserId, buffer) + } + + if (clientPools != null) { + if (!clientPools.containsKey(authenticatedUserId)) { + clientPools[authenticatedUserId] = mutableListOf() + } + clientPools[authenticatedUserId]?.add(webSocket) + } + + webSocket.handler(authenticatedHandler) + } else { + webSocket.writeTextMessage( + JsonObject() + .put("type", "auth_failure") + .put("message", "Authentication failed: User identification error.") + .encode() + ) + if (!firstAuthAttempt) { + logger.info { "Client $clientId failed to authenticate. Closing connection" } + webSocket.close() + } else { + firstAuthAttempt = false + logger.info { "Client $clientId failed to authenticate." } + timerId = this.setAuthTimer(authTimeOutMillis, authenticatedUserId, webSocket) + } + } + } + } catch (e: Exception) { + logger.error { "Client $clientId sent invalid auth message format: ${e.message}" } + webSocket.writeTextMessage( + JsonObject() + .put("type", "auth_failure") + .put("message", "Invalid authentication message format.") + .encode() + ) + webSocket.close() + } + + webSocket.closeHandler { _ -> + this.cancelTimer(timerId) + connectedClients.remove(clientId) + logger.info { "Client $clientId disconnected." } + if (authenticatedUserId != null) { + userIdToConnectionId.remove(authenticatedUserId) + } + + if (clientPools != null) { + clientPools[authenticatedUserId]?.remove(webSocket) + } + } + + webSocket.exceptionHandler { error -> + this.cancelTimer(timerId) + connectedClients.remove(clientId) + logger.error { "Error for client $clientId (User: $authenticatedUserId): ${error.message}" } + if (authenticatedUserId != null) { + userIdToConnectionId.remove(authenticatedUserId) + } + if (!webSocket.isClosed) { + webSocket.close() + } + } + } +} From d80f48ce9b7d7ccb867708950f8a9184178c5814 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:50:32 +0100 Subject: [PATCH 18/20] feat(websocket): add websocket router This commit introduces a websocket router for handling real-time communication. - Adds websocketRouter.kt to manage websocket routes. - Enables websocket page routing. - Adds a basic "/about" endpoint. This enhancement sets the foundation for future websocket features. --- .../websocket/router/websocketRouter.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/kotlin/com/ex_dock/ex_dock/websocket/router/websocketRouter.kt diff --git a/src/main/kotlin/com/ex_dock/ex_dock/websocket/router/websocketRouter.kt b/src/main/kotlin/com/ex_dock/ex_dock/websocket/router/websocketRouter.kt new file mode 100644 index 00000000..b7391934 --- /dev/null +++ b/src/main/kotlin/com/ex_dock/ex_dock/websocket/router/websocketRouter.kt @@ -0,0 +1,20 @@ +package com.ex_dock.ex_dock.websocket.router + +import com.ex_dock.ex_dock.backend.v1.router.auth.AuthProvider +import com.ex_dock.ex_dock.websocket.page.enableWebsocketPageRouter +import io.github.oshai.kotlinlogging.KLogger +import io.vertx.core.Vertx +import io.vertx.core.eventbus.DeliveryOptions +import io.vertx.ext.web.Router + +fun Router.enableWebsocketRouter(vertx: Vertx, logger: KLogger) { + val websocketRouter = Router.router(vertx) + + websocketRouter.enableWebsocketPageRouter(vertx, logger) + + websocketRouter["/about"].handler { ctx -> + ctx.end("about page for the Websocket") + } + + this.route().subRouter(websocketRouter) +} From b079bc43b39b0d05e2907a36a9cff77da8ec58a4 Mon Sep 17 00:00:00 2001 From: Kevin Kruijthof Date: Fri, 14 Nov 2025 14:50:37 +0100 Subject: [PATCH 19/20] feat(websocket): add WebsocketVerticle This commit introduces a new verticle for handling websocket connections. - Creates a basic WebsocketVerticle class. - Sets up a succeeded future for initial deployment. This provides a foundation for future websocket functionality. --- .../com/ex_dock/ex_dock/websocket/WebsocketVerticle.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/kotlin/com/ex_dock/ex_dock/websocket/WebsocketVerticle.kt diff --git a/src/main/kotlin/com/ex_dock/ex_dock/websocket/WebsocketVerticle.kt b/src/main/kotlin/com/ex_dock/ex_dock/websocket/WebsocketVerticle.kt new file mode 100644 index 00000000..370ea5df --- /dev/null +++ b/src/main/kotlin/com/ex_dock/ex_dock/websocket/WebsocketVerticle.kt @@ -0,0 +1,10 @@ +package com.ex_dock.ex_dock.websocket + +import io.vertx.core.Future +import io.vertx.core.VerticleBase + +class WebsocketVerticle: VerticleBase() { + override fun start(): Future<*>? { + return Future.succeededFuture() + } +} From fdb38f735fff96dc55c1371e1f49f34aa435eeea Mon Sep 17 00:00:00 2001 From: Rens Pols Date: Fri, 8 May 2026 22:02:20 +0200 Subject: [PATCH 20/20] chore: add additional remote repositories to `.idea/jarRepositories.xml` - Included Gatehill Software Snapshots and Sonatype OSS Snapshots repositories for enhanced dependency management. --- .idea/jarRepositories.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 712ab9d9..e8e2e68a 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -6,6 +6,11 @@