Big refactoring #77

Merged
flecomte merged 166 commits from refactoring-component-and-immutable into master 2021-03-24 19:06:07 +01:00
15 changed files with 219 additions and 160 deletions
Showing only changes of commit aa95de7a6a - Show all commits

View File

@@ -8,22 +8,31 @@ import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.joda.JodaModule
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
import fr.dcproject.application.Env.PROD
import fr.dcproject.component.article.articleKoinModule
import fr.dcproject.component.article.routes.installArticleRoutes
import fr.dcproject.component.auth.ForbiddenException
import fr.dcproject.component.auth.authKoinModule
import fr.dcproject.component.auth.jwt.jwtInstallation
import fr.dcproject.component.auth.routes.installAuthRoutes
import fr.dcproject.component.auth.user
import fr.dcproject.component.citizen.citizenKoinModule
import fr.dcproject.component.citizen.routes.installCitizenRoutes
import fr.dcproject.component.comment.article.routes.installCommentArticleRoutes
import fr.dcproject.component.comment.commentKoinModule
import fr.dcproject.component.comment.constitution.routes.installCommentConstitutionRoutes
import fr.dcproject.component.comment.generic.routes.installCommentRoutes
import fr.dcproject.component.constitution.constitutionKoinModule
import fr.dcproject.component.constitution.routes.installConstitutionRoutes
import fr.dcproject.component.follow.followKoinModule
import fr.dcproject.component.follow.routes.article.installFollowArticleRoutes
import fr.dcproject.component.follow.routes.constitution.installFollowConstitutionRoutes
import fr.dcproject.component.opinion.opinionKoinModule
import fr.dcproject.component.opinion.routes.installOpinionRoutes
import fr.dcproject.component.views.ConfigViews
import fr.dcproject.component.views.viewKoinModule
import fr.dcproject.component.vote.routes.installVoteRoutes
import fr.dcproject.component.vote.voteKoinModule
import fr.dcproject.component.workgroup.routes.installWorkgroupRoutes
import fr.dcproject.component.workgroup.workgroupKoinModule
import fr.dcproject.event.EventNotification
import fr.dcproject.event.EventSubscriber
import fr.dcproject.routes.definition
@@ -78,7 +87,21 @@ enum class Env { PROD, TEST, CUCUMBER }
fun Application.module(env: Env = PROD) {
install(Koin) {
Slf4jLog()
modules(KoinModule)
modules(
listOf(
KoinModule,
articleKoinModule,
authKoinModule,
citizenKoinModule,
commentKoinModule,
constitutionKoinModule,
followKoinModule,
opinionKoinModule,
viewKoinModule,
voteKoinModule,
workgroupKoinModule,
)
)
}
install(CallLogging) {
@@ -94,8 +117,6 @@ fun Application.module(env: Env = PROD) {
}
}
ConfigViews.createEsIndexForViews(get())
install(WebSockets) {
pingPeriod = Duration.ofSeconds(60) // Disabled (null) by default
timeout = Duration.ofSeconds(15)

View File

@@ -8,30 +8,6 @@ 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.component.article.ArticleAccessControl
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleViewManager
import fr.dcproject.component.auth.PasswordlessAuth
import fr.dcproject.component.auth.UserRepository
import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.component.comment.article.CommentArticleRepository
import fr.dcproject.component.comment.constitution.CommentConstitutionRepository
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.constitution.ConstitutionAccessControl
import fr.dcproject.component.constitution.ConstitutionRepository
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.component.opinion.OpinionChoiceAccessControl
import fr.dcproject.component.opinion.OpinionChoiceRepository
import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.VoteArticleRepository
import fr.dcproject.component.vote.VoteCommentRepository
import fr.dcproject.component.vote.VoteConstitutionRepository
import fr.dcproject.component.vote.VoteRepository
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.event.publisher.Publisher
import fr.dcproject.messages.Mailer
import fr.dcproject.messages.NotificationEmailSender
@@ -43,14 +19,8 @@ import io.ktor.client.features.websocket.WebSockets
import io.ktor.util.KtorExperimentalAPI
import io.lettuce.core.RedisClient
import io.lettuce.core.api.async.RedisAsyncCommands
import org.apache.http.HttpHost
import org.elasticsearch.client.RestClient
import org.koin.core.qualifier.named
import org.koin.dsl.module
import fr.dcproject.component.comment.generic.CommentRepository as CommentGenericRepository
import fr.dcproject.component.follow.FollowArticleRepository as FollowArticleRepository
import fr.dcproject.component.follow.FollowConstitutionRepository as FollowConstitutionRepository
import fr.dcproject.component.opinion.OpinionRepositoryArticle as OpinionArticleRepository
@KtorExperimentalAPI
val KoinModule = module {
@@ -105,50 +75,9 @@ val KoinModule = module {
).createRequester()
}
// Repositories
single { UserRepository(get()) }
single { ArticleRepository(get()) }
single { CitizenRepository(get()) }
single { ConstitutionRepository(get()) }
single { FollowArticleRepository(get()) }
single { FollowConstitutionRepository(get()) }
single { CommentGenericRepository(get()) }
single { CommentArticleRepository(get()) }
single { CommentConstitutionRepository(get()) }
single { VoteRepository(get()) }
single { VoteArticleRepository(get()) }
single { VoteConstitutionRepository(get()) }
single { VoteCommentRepository(get()) }
single { OpinionChoiceRepository(get()) }
single { OpinionArticleRepository(get()) }
single { WorkgroupRepository(get()) }
// AccessControl
single { ArticleAccessControl(get()) }
single { CitizenAccessControl() }
single { CommentAccessControl() }
single { WorkgroupAccessControl() }
single { ConstitutionAccessControl() }
single { VoteAccessControl() }
single { FollowAccessControl() }
single { OpinionAccessControl() }
single { OpinionChoiceAccessControl() }
// Elasticsearch Client
single<RestClient> {
RestClient.builder(
HttpHost.create(Configuration.elasticsearch)
).build()
}
single { ArticleViewManager<ArticleForView>(get()) }
// Mailer
single { Mailer(Configuration.sendGridKey) }
// Used to send a connexion link by email
single { PasswordlessAuth(get<Mailer>(), Configuration.domain, get()) }
single { Publisher(get(), get(), exchangeName = Configuration.exchangeNotificationName) }
single { NotificationEmailSender(get<Mailer>(), Configuration.domain, get(), get()) }

View File

@@ -0,0 +1,8 @@
package fr.dcproject.component.article
import org.koin.dsl.module
val articleKoinModule = module {
single { ArticleRepository(get()) }
single { ArticleAccessControl(get()) }
}

View File

@@ -0,0 +1,11 @@
package fr.dcproject.component.auth
import fr.dcproject.application.Configuration
import fr.dcproject.messages.Mailer
import org.koin.dsl.module
val authKoinModule = module {
single { UserRepository(get()) }
// Used to send a connexion link by email
single { PasswordlessAuth(get<Mailer>(), Configuration.domain, get()) }
}

View File

@@ -0,0 +1,8 @@
package fr.dcproject.component.citizen
import org.koin.dsl.module
val citizenKoinModule = module {
single { CitizenRepository(get()) }
single { CitizenAccessControl() }
}

View File

@@ -0,0 +1,14 @@
package fr.dcproject.component.comment
import fr.dcproject.component.comment.article.CommentArticleRepository
import fr.dcproject.component.comment.constitution.CommentConstitutionRepository
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.CommentRepository
import org.koin.dsl.module
val commentKoinModule = module {
single { CommentRepository(get()) }
single { CommentArticleRepository(get()) }
single { CommentConstitutionRepository(get()) }
single { CommentAccessControl() }
}

View File

@@ -0,0 +1,8 @@
package fr.dcproject.component.constitution
import org.koin.dsl.module
val constitutionKoinModule = module {
single { ConstitutionRepository(get()) }
single { ConstitutionAccessControl() }
}

View File

@@ -0,0 +1,9 @@
package fr.dcproject.component.follow
import org.koin.dsl.module
val followKoinModule = module {
single { FollowArticleRepository(get()) }
single { FollowConstitutionRepository(get()) }
single { FollowAccessControl() }
}

View File

@@ -0,0 +1,11 @@
package fr.dcproject.component.opinion
import org.koin.dsl.module
val opinionKoinModule = module {
single { OpinionChoiceRepository(get()) }
single { OpinionRepositoryArticle(get()) }
single { OpinionAccessControl() }
single { OpinionChoiceAccessControl() }
}

View File

@@ -1,85 +0,0 @@
package fr.dcproject.component.views
import org.elasticsearch.client.Request
import org.elasticsearch.client.RestClient
import org.slf4j.Logger
import org.slf4j.LoggerFactory
object ConfigViews {
private fun waitElasticsearchIsUp(client: RestClient) {
val logger: Logger = LoggerFactory.getLogger("fr.dcproject.elasticsearch")
val request = Request("GET", "/_cluster/health")
repeat(5 * 60 / 2) { // 5 minutes
runCatching {
client.performRequest(request).statusLine.statusCode
}.onSuccess {
if (it == 200) {
logger.debug("Elasticsearch is Ready! Continue...")
return
} else {
logger.debug("sleep 2s and retry...")
Thread.sleep(2000)
}
}.onFailure {
logger.debug("${it.message}, sleep 2s and retry...")
Thread.sleep(2000)
}
}
error("Elasticsearch is not ready")
}
fun createEsIndexForViews(client: RestClient) {
waitElasticsearchIsUp(client)
/* Create index if not exist */
client.run {
if (performRequest(Request("HEAD", "/views?include_type_name=false")).statusLine.statusCode == 404) {
Request(
"PUT",
"/views?include_type_name=false"
).apply {
//language=JSON
setJsonEntity(
"""
{
"settings": {
"number_of_shards": 5
},
"mappings": {
"properties": {
"logged": {
"type": "boolean"
},
"type": {
"type": "keyword"
},
"user_ref": {
"type": "keyword"
},
"id": {
"type": "keyword"
},
"version_id": {
"type": "keyword"
},
"ip": {
"type": "keyword"
},
"citizen_id": {
"type": "keyword"
},
"view_at": {
"type": "date"
}
}
}
}
""".trimIndent()
)
}.let {
performRequest(it)
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
package fr.dcproject.component.views
import fr.dcproject.application.Configuration
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleViewManager
import org.apache.http.HttpHost
import org.elasticsearch.client.RestClient
import org.koin.dsl.module
val viewKoinModule = module {
// Elasticsearch Client
val esClient = RestClient.builder(
HttpHost.create(Configuration.elasticsearch)
).build().apply {
createEsIndexForViews()
}
single { ArticleViewManager<ArticleForView>(esClient) }
}

View File

@@ -0,0 +1,58 @@
package fr.dcproject.component.views
import fr.dcproject.utils.waitElasticsearchIsUp
import org.elasticsearch.client.Request
import org.elasticsearch.client.RestClient
fun RestClient.createEsIndexForViews() {
waitElasticsearchIsUp()
/* Create index if not exist */
if (performRequest(Request("HEAD", "/views?include_type_name=false")).statusLine.statusCode == 404) {
Request(
"PUT",
"/views?include_type_name=false"
).apply {
//language=JSON
setJsonEntity(
"""
{
"settings": {
"number_of_shards": 5
},
"mappings": {
"properties": {
"logged": {
"type": "boolean"
},
"type": {
"type": "keyword"
},
"user_ref": {
"type": "keyword"
},
"id": {
"type": "keyword"
},
"version_id": {
"type": "keyword"
},
"ip": {
"type": "keyword"
},
"citizen_id": {
"type": "keyword"
},
"view_at": {
"type": "date"
}
}
}
}
""".trimIndent()
)
}.let {
performRequest(it)
}
}
}

View File

@@ -0,0 +1,12 @@
package fr.dcproject.component.vote
import org.koin.dsl.module
val voteKoinModule = module {
single { VoteRepository(get()) }
single { VoteArticleRepository(get()) }
single { VoteConstitutionRepository(get()) }
single { VoteCommentRepository(get()) }
single { VoteAccessControl() }
}

View File

@@ -0,0 +1,8 @@
package fr.dcproject.component.workgroup
import org.koin.dsl.module
val workgroupKoinModule = module {
single { WorkgroupRepository(get()) }
single { WorkgroupAccessControl() }
}

View File

@@ -0,0 +1,28 @@
package fr.dcproject.utils
import org.elasticsearch.client.Request
import org.elasticsearch.client.RestClient
import org.slf4j.Logger
import org.slf4j.LoggerFactory
fun RestClient.waitElasticsearchIsUp() {
val logger: Logger = LoggerFactory.getLogger("fr.dcproject.elasticsearch")
val request = Request("GET", "/_cluster/health")
repeat(5 * 60 / 2) { // 5 minutes
runCatching {
performRequest(request).statusLine.statusCode
}.onSuccess {
if (it == 200) {
logger.debug("Elasticsearch is Ready! Continue...")
return
} else {
logger.debug("sleep 2s and retry...")
Thread.sleep(2000)
}
}.onFailure {
logger.debug("${it.message}, sleep 2s and retry...")
Thread.sleep(2000)
}
}
error("Elasticsearch is not ready")
}