Publish message into rabbitmq on create article
Create Redis and Rabbit in docker-compose
This commit is contained in:
9
.env
9
.env
@@ -4,6 +4,7 @@ DATABASE_URL=jdbc:postgresql:dc-project
|
||||
|
||||
APP_PORT=8080
|
||||
OPENAPI_PORT=8181
|
||||
SONARQUBE_PORT=9000
|
||||
|
||||
ELASTIC_REST=9200
|
||||
ELASTIC_NODES=9300
|
||||
@@ -14,3 +15,11 @@ DB_PORT=5432
|
||||
DB_NAME=dc-project
|
||||
DB_USER=dc-project
|
||||
DB_PWD=dc-project
|
||||
|
||||
REDIS_PORT=6379
|
||||
REDIS_CONNECTION=redis://localhost:6379
|
||||
REDIS_COMMANDER_PORT=8081
|
||||
|
||||
RABBITMQ_PORT=5672
|
||||
RABBITMQ_CONNECTION=amqp://localhost:5672
|
||||
RABBITMQ_MANAGEMENT_PORT=15672
|
||||
@@ -3,6 +3,7 @@ import org.owasp.dependencycheck.reporting.ReportGenerator
|
||||
|
||||
val ktor_version: String by project
|
||||
val kotlin_version: String by project
|
||||
val coroutinesVersion: String by project
|
||||
val logback_version: String by project
|
||||
val koinVersion: String by project
|
||||
val postgresjson_version: String by project
|
||||
@@ -66,6 +67,8 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}")
|
||||
implementation("io.ktor:ktor-server-jetty:$ktor_version")
|
||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
||||
implementation("io.ktor:ktor-server-core:$ktor_version")
|
||||
@@ -83,6 +86,8 @@ dependencies {
|
||||
implementation("com.github.jasync-sql:jasync-postgresql:1.0.7")
|
||||
implementation("fr.postgresjson:postgresjson:$postgresjson_version")
|
||||
implementation("com.sendgrid:sendgrid-java:4.4.1")
|
||||
implementation("io.lettuce:lettuce-core:5.2.2.RELEASE")
|
||||
implementation("com.rabbitmq:amqp-client:5.8.0")
|
||||
|
||||
testImplementation("io.ktor:ktor-server-tests:$ktor_version")
|
||||
testImplementation("io.ktor:ktor-client-mock:$ktor_version")
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
image: sonarqube
|
||||
restart: always
|
||||
ports:
|
||||
- 9000:9000
|
||||
- ${SONARQUBE_PORT}:9000
|
||||
|
||||
openapi:
|
||||
container_name: openapi_${NAME}
|
||||
@@ -18,6 +18,21 @@ services:
|
||||
environment:
|
||||
URL: "http://localhost:8080"
|
||||
|
||||
rabbitmq:
|
||||
container_name: rabbitmq_${NAME}
|
||||
image: rabbitmq:management-alpine
|
||||
restart: always
|
||||
ports:
|
||||
- ${RABBITMQ_PORT}:5672
|
||||
- ${RABBITMQ_MANAGEMENT_PORT}:15672
|
||||
|
||||
redis:
|
||||
container_name: redis_${NAME}
|
||||
image: redis:6.0-rc-alpine
|
||||
restart: always
|
||||
ports:
|
||||
- ${REDIS_PORT}:6379
|
||||
|
||||
app:
|
||||
container_name: app_${NAME}
|
||||
build:
|
||||
@@ -29,9 +44,13 @@ services:
|
||||
environment:
|
||||
DB_HOST: db
|
||||
SEND_GRID_KEY: ${SEND_GRID_KEY}
|
||||
REDIS_CONNECTION: redis://redis:6379
|
||||
RABBITMQ_CONNECTION: amqp://rabbitmq:5671
|
||||
depends_on:
|
||||
- elasticsearch
|
||||
- db
|
||||
- redis
|
||||
- rabbitmq
|
||||
|
||||
elasticsearch:
|
||||
container_name: elasticsearch_${NAME}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
ktor_version=1.2.2
|
||||
kotlin.code.style=official
|
||||
kotlin_version=1.3.40
|
||||
coroutinesVersion=1.3.3
|
||||
logback_version=1.2.1
|
||||
postgresjson_version=0.1
|
||||
koinVersion=2.0.1
|
||||
|
||||
@@ -7,8 +7,12 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import com.fasterxml.jackson.datatype.joda.JodaModule
|
||||
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import fr.dcproject.Env.PROD
|
||||
import fr.dcproject.entity.*
|
||||
import fr.dcproject.event.EntityEvent
|
||||
import fr.dcproject.event.EventNotification
|
||||
import fr.dcproject.event.publisher.Publisher
|
||||
import fr.dcproject.routes.*
|
||||
import fr.dcproject.security.voter.*
|
||||
import fr.postgresjson.migration.Migrations
|
||||
@@ -27,7 +31,9 @@ import io.ktor.jackson.jackson
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Locations
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.Routing
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.eclipse.jetty.util.log.Slf4jLog
|
||||
import org.koin.ktor.ext.Koin
|
||||
@@ -148,6 +154,25 @@ fun Application.module(env: Env = PROD) {
|
||||
)
|
||||
}
|
||||
|
||||
install(EventNotification) {
|
||||
/* Config Rabbit */
|
||||
val exchangeName = config.exchangeNotificationName
|
||||
get<ConnectionFactory>().newConnection().use { connection -> connection.createChannel().use { channel ->
|
||||
channel.queueDeclare("sse", true, false, false, null)
|
||||
channel.queueDeclare("email", true, false, false, null)
|
||||
channel.exchangeDeclare(exchangeName, "direct")
|
||||
channel.queueBind("sse", exchangeName, "")
|
||||
channel.queueBind("email", exchangeName, "")
|
||||
}}
|
||||
|
||||
/* Declare publisher on event */
|
||||
val publisher = Publisher(get(), get())
|
||||
subscribe(EntityEvent.Type.UPDATE_ARTICLE.event) {
|
||||
println("Article is updated ${it.target.id}")
|
||||
publisher.publish(it)
|
||||
}
|
||||
}
|
||||
|
||||
install(Authentication) {
|
||||
/**
|
||||
* Setup the JWT authentication to be used in [Routing].
|
||||
@@ -200,6 +225,20 @@ fun Application.module(env: Env = PROD) {
|
||||
opinionArticle(get())
|
||||
opinionChoice(get())
|
||||
definition()
|
||||
get("/sse") {
|
||||
// environment.monitor.raise(EntityEvent.Type.UPDATE_ARTICLE.event, ArticleUpdate(ArticleRef()))
|
||||
// val redis = this@authenticate.getKoin().get<RedisReactiveCommands<String, String>>()
|
||||
// redis.set("key", "test").awaitSingle()
|
||||
// redis.lpush("list", "test2").asFlow().map {
|
||||
// println(it)
|
||||
// }.collect()
|
||||
// redis.get("key").asFlow().collect { println(it) }
|
||||
// redis.rpop("list").asFlow().collect {
|
||||
// println(it)
|
||||
// call.respondText { it }
|
||||
// }
|
||||
call.respondText("OK")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ class Config {
|
||||
var username: String = config.getString("db.username")
|
||||
var password: String = config.getString("db.password")
|
||||
val port: Int = config.getInt("db.port")
|
||||
|
||||
val redis: String = config.getString("redis.connection")
|
||||
val rabbitmq: String = config.getString("rabbitmq.connection")
|
||||
val exchangeNotificationName = "notification"
|
||||
val sendGridKey: String = config.getString("mail.sendGrid.key")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
package fr.dcproject
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.datatype.joda.JodaModule
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import fr.dcproject.messages.Mailer
|
||||
import fr.dcproject.messages.SsoManager
|
||||
import fr.postgresjson.connexion.Connection
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import io.lettuce.core.RedisClient
|
||||
import io.lettuce.core.api.reactive.RedisReactiveCommands
|
||||
import org.koin.dsl.module
|
||||
import fr.dcproject.repository.Article as ArticleRepository
|
||||
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||
@@ -39,6 +49,25 @@ val Module = module {
|
||||
)
|
||||
}
|
||||
|
||||
single<RedisReactiveCommands<String, String>> {
|
||||
RedisClient.create(config.redis).connect()?.reactive() ?: error("Unable to connect to redis")
|
||||
}
|
||||
|
||||
single<ConnectionFactory> {
|
||||
ConnectionFactory().apply { setUri(config.rabbitmq) }
|
||||
}
|
||||
|
||||
single<ObjectMapper> {
|
||||
jacksonObjectMapper().apply {
|
||||
registerModule(SimpleModule())
|
||||
propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE
|
||||
|
||||
registerModule(JodaModule())
|
||||
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
|
||||
}
|
||||
}
|
||||
|
||||
single {
|
||||
Requester.RequesterFactory(
|
||||
connection = get(),
|
||||
|
||||
54
src/main/kotlin/fr/dcproject/event/EventNotification.kt
Normal file
54
src/main/kotlin/fr/dcproject/event/EventNotification.kt
Normal file
@@ -0,0 +1,54 @@
|
||||
package fr.dcproject.event
|
||||
|
||||
import fr.dcproject.entity.Article
|
||||
import fr.postgresjson.entity.immutable.UuidEntity
|
||||
import io.ktor.application.*
|
||||
import io.ktor.util.AttributeKey
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
import org.joda.time.DateTime
|
||||
|
||||
abstract class Notification(
|
||||
val type: String,
|
||||
val createdAt: DateTime = DateTime.now()
|
||||
)
|
||||
abstract class EntityEvent(
|
||||
val target: UuidEntity,
|
||||
type: String,
|
||||
val action: String
|
||||
) : Notification(type) {
|
||||
enum class Type(val event: EventDefinition<ArticleUpdate>) {
|
||||
UPDATE_ARTICLE(EventDefinition<ArticleUpdate>())
|
||||
}
|
||||
}
|
||||
|
||||
class ArticleUpdate(
|
||||
target: Article
|
||||
) : EntityEvent(target, "article", "update")
|
||||
|
||||
/**
|
||||
* Installation Class
|
||||
*/
|
||||
class EventNotification {
|
||||
class Configuration(private val monitor: ApplicationEvents) {
|
||||
private val subscribers = mutableListOf<DisposableHandle>()
|
||||
fun <T: Notification> subscribe(definition: EventDefinition<T>, handler: EventHandler<T>): DisposableHandle {
|
||||
return monitor.subscribe(definition, handler).also {
|
||||
subscribers.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object Feature : ApplicationFeature<Application, Configuration, EventNotification> {
|
||||
override val key = AttributeKey<EventNotification>("EventNotification")
|
||||
|
||||
@KtorExperimentalAPI
|
||||
override fun install(
|
||||
pipeline: Application,
|
||||
configure: Configuration.() -> Unit
|
||||
): EventNotification {
|
||||
Configuration(pipeline.environment.monitor).apply(configure)
|
||||
return EventNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/main/kotlin/fr/dcproject/event/publisher/Publisher.kt
Normal file
30
src/main/kotlin/fr/dcproject/event/publisher/Publisher.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
package fr.dcproject.event.publisher
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import fr.dcproject.config
|
||||
import fr.dcproject.event.EntityEvent
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class Publisher(
|
||||
private val mapper: ObjectMapper,
|
||||
private val factory: ConnectionFactory,
|
||||
private val logger: Logger = LoggerFactory.getLogger(Publisher::class.qualifiedName)
|
||||
) {
|
||||
fun <T: EntityEvent>publish(it: T): Job {
|
||||
return GlobalScope.launch {
|
||||
factory.newConnection().use { connection -> connection.createChannel().use { channel ->
|
||||
channel.basicPublish(config.exchangeNotificationName, "", null, it.serialize().toByteArray())
|
||||
logger.debug("Publish message ${it.target.id}")
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
private fun EntityEvent.serialize(): String {
|
||||
return mapper.writeValueAsString(this) ?: error("Unable tu serialize message")
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package fr.dcproject.routes
|
||||
|
||||
import fr.dcproject.citizen
|
||||
import fr.dcproject.event.ArticleUpdate
|
||||
import fr.dcproject.event.EntityEvent
|
||||
import fr.dcproject.repository.Article.Filter
|
||||
import fr.dcproject.security.voter.ArticleVoter.Action.CREATE
|
||||
import fr.dcproject.security.voter.ArticleVoter.Action.VIEW
|
||||
import fr.dcproject.security.voter.assertCan
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import io.ktor.application.application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -83,6 +86,7 @@ fun Route.article(repo: ArticleRepository) {
|
||||
assertCan(CREATE, article)
|
||||
|
||||
repo.upsert(article)
|
||||
application.environment.monitor.raise(EntityEvent.Type.UPDATE_ARTICLE.event, ArticleUpdate(article))
|
||||
|
||||
call.respond(article)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,16 @@ db {
|
||||
port = 5432
|
||||
}
|
||||
|
||||
redis {
|
||||
connection = "redis://localhost:6379"
|
||||
connection = ${?REDIS_CONNECTION}
|
||||
}
|
||||
|
||||
rabbitmq {
|
||||
connection = "amqp://localhost:5672"
|
||||
connection = ${?RABBITMQ_CONNECTION}
|
||||
}
|
||||
|
||||
mail {
|
||||
sendGrid {
|
||||
key = ${?SEND_GRID_KEY}
|
||||
|
||||
@@ -1132,7 +1132,7 @@ components:
|
||||
required: true
|
||||
example:
|
||||
Lorem upsum...
|
||||
descritption:
|
||||
description:
|
||||
type: string
|
||||
required: true
|
||||
example:
|
||||
|
||||
Reference in New Issue
Block a user