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 @@
+
+
+
+
+
@@ -16,5 +21,10 @@
+
+
+
+
+
\ No newline at end of file
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 7e5879fc..b983f587 100644
--- a/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt
+++ b/src/main/kotlin/com/ex_dock/ex_dock/MainVerticle.kt
@@ -1,15 +1,18 @@
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
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
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
@@ -50,12 +53,13 @@ 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 staticHandler = StaticHandler.create("webroot")
- .setCachingEnabled(false)
+ 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 ->
@@ -94,7 +98,7 @@ class MainVerticle : VerticleBase() {
ctx -> ctx.response().sendFile("swagger.json")
}
- mainRouter.enableBackendRouter(vertx, logger)
+ mainRouter.enableBackendRouter(vertx, logger, authProvider)
mainRouter.initHome(eventBus)
mainRouter.initProduct(vertx)
@@ -103,17 +107,41 @@ class MainVerticle : VerticleBase() {
mainRouter.initCheckout(vertx)
mainRouter.initAccount(vertx)
+ frontendRouter.enableFrontendRouter(vertx, logger, authProvider)
+ websocketRouter.enableWebsocketRouter(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")}" }
+ }
+
+ 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()
.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 { _ ->
- logger.info { "HTTP server started on port ${props.getProperty("FRONTEND_PORT")}" }
+ }.onSuccess { http ->
+ logger.info { "HTTP server started on port ${props.getProperty("BACKEND_PORT")}" }
}
}
}
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()
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..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
@@ -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
@@ -32,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())
@@ -152,6 +152,7 @@ fun Router.enableBackendV1Router(vertx: Vertx, absoluteMounting: Boolean = false
backendV1Router.initProductsRouter(vertx)
backendV1Router.initTemplateRouter(vertx)
backendV1Router.enablePagesRouter(vertx)
+ backendV1Router.initUrlRouter(vertx)
this.route(
@@ -170,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" }
}
}
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..23764475
--- /dev/null
+++ b/src/main/kotlin/com/ex_dock/ex_dock/backend/v1/router/url/UrlRouter.kt
@@ -0,0 +1,71 @@
+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 {
+ 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 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().toDocument().encode())
+ }
+ }
+
+ urlRouter.put("/update").handler { ctx ->
+ 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().toDocument().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/JDBCStarter.kt b/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt
index 83a7d423..44efc4c6 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
@@ -101,6 +103,7 @@ class JDBCStarter : VerticleBase() {
verticles.add(vertx.deployWorkerVerticleHelper(SystemVerticle::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"))
}
@@ -126,6 +129,7 @@ class JDBCStarter : VerticleBase() {
.registerGenericCodec(List::class)
.registerGenericCodec(SingleUseTemplateData::class)
.registerGenericCodec(ArrayList::class)
+ .registerGenericCodec(UrlKeys::class)
.registerGenericListCodec(FullUser::class)
.registerGenericListCodec(JsonObject::class)
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())
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)
+ }
+ }
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..c21d17f4
--- /dev/null
+++ b/src/main/kotlin/com/ex_dock/ex_dock/frontend/router/frontendRouter.kt
@@ -0,0 +1,82 @@
+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.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.core.json.JsonObject
+import io.vertx.ext.web.Router
+import io.vertx.ext.web.handler.CorsHandler
+
+fun Router.enableFrontendRouter(vertx: Vertx, logger: KLogger, authProvider: AuthProvider) {
+ val frontendRouter: Router = Router.router(vertx)
+ val pairDeliveryOptions = DeliveryOptions().setCodecName("PairCodec")
+ val eventBus = vertx.eventBus()
+
+ eventBus.send(
+ "process.authentication.registerKeys",
+ Pair(authProvider.privateKey, authProvider.publicKey),
+ pairDeliveryOptions
+ )
+
+ 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 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)
+}
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
}
}
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..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
@@ -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
@@ -26,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
@@ -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
@@ -43,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) }
@@ -50,34 +54,10 @@ class TemplateEngineVerticle : VerticleBase() {
orderCache = AsyncExDockCache(vertx) { key -> getOrderCacheData(key) }
shipmentCache = AsyncExDockCache(vertx) { key -> getShipmentCacheData(key) }
transactionCache = AsyncExDockCache(vertx) { key -> getTransactionCacheData(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
- }
- })
+ listCache = AsyncExDockCache(vertx) { key -> getListCacheData(key) }
singleUseTemplate()
getCompiledTemplate()
- invalidateCacheKey()
return Future.succeededFuture()
}
@@ -119,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
@@ -138,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(
@@ -176,7 +135,8 @@ class TemplateEngineVerticle : VerticleBase() {
invoiceCache,
orderCache,
shipmentCache,
- transactionCache
+ transactionCache,
+ listCache
)
val futureMap: MutableMap> = mutableMapOf()
@@ -195,6 +155,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)
@@ -216,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,
@@ -272,6 +242,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 +260,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(
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
}
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()
+ }
+ }
+ }
+}
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())
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()
+ }
+}
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)
+}
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)
+}