Big refactoring #77

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

View File

@@ -26,7 +26,8 @@ plugins {
maven maven
id("maven-publish") id("maven-publish")
id("org.jetbrains.kotlin.jvm") version "1.3.50" kotlin("jvm") version "1.4.21"
kotlin("plugin.serialization") version "1.4.21"
id("com.github.johnrengelman.shadow") version "5.2.0" id("com.github.johnrengelman.shadow") version "5.2.0"
id("org.jlleitschuh.gradle.ktlint") version "8.2.0" id("org.jlleitschuh.gradle.ktlint") version "8.2.0"
@@ -123,6 +124,7 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
implementation("io.ktor:ktor-server-jetty:$ktor_version") implementation("io.ktor:ktor-server-jetty:$ktor_version")
implementation("io.ktor:ktor-client-jetty:$ktor_version") implementation("io.ktor:ktor-client-jetty:$ktor_version")
implementation("ch.qos.logback:logback-classic:$logback_version") implementation("ch.qos.logback:logback-classic:$logback_version")
@@ -139,8 +141,8 @@ dependencies {
implementation("com.auth0:java-jwt:3.12.0") implementation("com.auth0:java-jwt:3.12.0")
implementation("com.github.jasync-sql:jasync-postgresql:1.1.6") implementation("com.github.jasync-sql:jasync-postgresql:1.1.6")
implementation("com.github.flecomte:postgres-json:2.0.0") implementation("com.github.flecomte:postgres-json:2.0.0")
implementation("com.sendgrid:sendgrid-java:4.4.1") implementation("com.sendgrid:sendgrid-java:4.7.1")
implementation("io.lettuce:lettuce-core:5.3.6.RELEASE") //TODO update to 6.0.2 implementation("io.lettuce:lettuce-core:5.3.6.RELEASE") // TODO update to 6.0.2
implementation("com.rabbitmq:amqp-client:5.10.0") implementation("com.rabbitmq:amqp-client:5.10.0")
implementation("org.elasticsearch.client:elasticsearch-rest-client:6.7.1") implementation("org.elasticsearch.client:elasticsearch-rest-client:6.7.1")
implementation("com.jayway.jsonpath:json-path:2.5.0") implementation("com.jayway.jsonpath:json-path:2.5.0")

View File

@@ -1,6 +1,6 @@
ktor_version=1.2.2 ktor_version=1.5.0
kotlin.code.style=official kotlin.code.style=official
kotlin_version=1.3.40 kotlin_version=1.4.21-2
coroutinesVersion=1.4.2 coroutinesVersion=1.4.2
logback_version=1.2.3 logback_version=1.2.3
koinVersion=2.0.1 koinVersion=2.0.1

View File

@@ -49,6 +49,7 @@ import io.ktor.client.*
import io.ktor.client.engine.jetty.Jetty import io.ktor.client.engine.jetty.Jetty
import io.ktor.features.* import io.ktor.features.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.http.cio.websocket.*
import io.ktor.jackson.* import io.ktor.jackson.*
import io.ktor.locations.* import io.ktor.locations.*
import io.ktor.response.* import io.ktor.response.*
@@ -117,10 +118,12 @@ fun Application.module(env: Env = PROD) {
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
configure(SerializationFeature.INDENT_OUTPUT, true) configure(SerializationFeature.INDENT_OUTPUT, true)
setDefaultPrettyPrinter(DefaultPrettyPrinter().apply { setDefaultPrettyPrinter(
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance) DefaultPrettyPrinter().apply {
indentObjectsWith(DefaultIndenter(" ", "\n")) indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
}) indentObjectsWith(DefaultIndenter(" ", "\n"))
}
)
} }
} }

View File

@@ -123,4 +123,4 @@ val converters: ConverterDeclaration = {
?: throw NotFoundException("Workgroup $values not found") ?: throw NotFoundException("Workgroup $values not found")
} }
} }
} }

View File

@@ -1,10 +1,10 @@
package fr.dcproject.component.article package fr.dcproject.component.article
import fr.dcproject.component.citizen.*
import fr.dcproject.component.workgroup.WorkgroupCart import fr.dcproject.component.workgroup.WorkgroupCart
import fr.dcproject.component.workgroup.WorkgroupCartI import fr.dcproject.component.workgroup.WorkgroupCartI
import fr.dcproject.component.workgroup.WorkgroupRef import fr.dcproject.component.workgroup.WorkgroupRef
import fr.dcproject.component.workgroup.WorkgroupSimple import fr.dcproject.component.workgroup.WorkgroupSimple
import fr.dcproject.component.citizen.*
import fr.dcproject.entity.* import fr.dcproject.entity.*
import fr.postgresjson.entity.* import fr.postgresjson.entity.*
import org.joda.time.DateTime import org.joda.time.DateTime
@@ -128,4 +128,4 @@ interface ArticleAuthI<U : CitizenI> :
CreatedBy<U>, CreatedBy<U>,
EntityDeletedAt { EntityDeletedAt {
val draft: Boolean val draft: Boolean
} }

View File

@@ -30,7 +30,8 @@ class ArticleRepository(override var requester: Requester) : RepositoryI {
return requester return requester
.getFunction("find_articles") .getFunction("find_articles")
.select( .select(
page, limit, page,
limit,
"sort" to sort?.toSnakeCase(), "sort" to sort?.toSnakeCase(),
"direction" to direction, "direction" to direction,
"search" to search, "search" to search,
@@ -48,4 +49,4 @@ class ArticleRepository(override var requester: Requester) : RepositoryI {
val createdById: String? = null, val createdById: String? = null,
val workgroupId: String? = null val workgroupId: String? = null
) : Parameter ) : Parameter
} }

View File

@@ -1,11 +1,11 @@
package fr.dcproject.component.article package fr.dcproject.component.article
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.views.ViewManager
import fr.dcproject.entity.ViewAggregation import fr.dcproject.entity.ViewAggregation
import fr.dcproject.utils.contentToString import fr.dcproject.utils.contentToString
import fr.dcproject.utils.getJsonField import fr.dcproject.utils.getJsonField
import fr.dcproject.utils.toIso import fr.dcproject.utils.toIso
import fr.dcproject.component.views.ViewManager
import org.elasticsearch.client.Request import org.elasticsearch.client.Request
import org.elasticsearch.client.Response import org.elasticsearch.client.Response
import org.elasticsearch.client.RestClient import org.elasticsearch.client.RestClient
@@ -27,7 +27,8 @@ class ArticleViewManager(private val restClient: RestClient) : ViewManager<Artic
"/views/_doc/" "/views/_doc/"
).apply { ).apply {
//language=JSON //language=JSON
setJsonEntity(""" setJsonEntity(
"""
{ {
"logged": $isLogged, "logged": $isLogged,
"type": "article", "type": "article",
@@ -38,7 +39,8 @@ class ArticleViewManager(private val restClient: RestClient) : ViewManager<Artic
"citizen_id": "${citizen?.id}", "citizen_id": "${citizen?.id}",
"view_at": "${dateTime.toIso()}" "view_at": "${dateTime.toIso()}"
} }
""".trimIndent()) """.trimIndent()
)
} }
return restClient.performRequest(request) return restClient.performRequest(request)
@@ -53,7 +55,8 @@ class ArticleViewManager(private val restClient: RestClient) : ViewManager<Artic
"/views/_search" "/views/_search"
).apply { ).apply {
//language=JSON //language=JSON
setJsonEntity(""" setJsonEntity(
"""
{ {
"size": 0, "size": 0,
"query": { "query": {
@@ -81,7 +84,8 @@ class ArticleViewManager(private val restClient: RestClient) : ViewManager<Artic
} }
} }
} }
""".trimIndent()) """.trimIndent()
)
} }
return restClient return restClient
@@ -92,4 +96,4 @@ class ArticleViewManager(private val restClient: RestClient) : ViewManager<Artic
) )
} }
} }
} }

View File

@@ -33,10 +33,10 @@ class ArticleVoter(private val articleRepo: ArticleRepository) : Voter() {
/* The new Article must by created by the same citizen of the connected citizen */ /* The new Article must by created by the same citizen of the connected citizen */
if (subject.createdBy.id == citizen.id) { if (subject.createdBy.id == citizen.id) {
/* The creator must be the same of the creator of preview version of article */ /* The creator must be the same of the creator of preview version of article */
val lastVersionId = articleRepo val lastVersionId = articleRepo
.findVersionsByVersionId(1, 1, subject.versionId) .findVersionsByVersionId(1, 1, subject.versionId)
.result .result
.firstOrNull()?.createdBy?.id .firstOrNull()?.createdBy?.id
return when (lastVersionId) { return when (lastVersionId) {
null -> granted("You can create a new Article") null -> granted("You can create a new Article")
@@ -46,4 +46,4 @@ class ArticleVoter(private val articleRepo: ArticleRepository) : Voter() {
} }
return denied("This article must be yours for update it", "article.update.notYours") return denied("This article must be yours for update it", "article.update.notYours")
} }
} }

View File

@@ -65,4 +65,4 @@ fun Route.getOneArticle(viewManager: ArticleViewManager, voter: ArticleVoter) {
viewManager.addView(call.request.local.remoteHost, it.article, citizenOrNull) viewManager.addView(call.request.local.remoteHost, it.article, citizenOrNull)
} }
} }
} }

View File

@@ -54,4 +54,4 @@ class PasswordlessAuth(
} }
private fun noEmail(email: String): Nothing = throw EmailNotFound(email) private fun noEmail(email: String): Nothing = throw EmailNotFound(email)
} }

View File

@@ -12,7 +12,8 @@ class User(
blockedAt: DateTime? = null, blockedAt: DateTime? = null,
override var plainPassword: String? = null, override var plainPassword: String? = null,
override var roles: List<Roles> = emptyList() override var roles: List<Roles> = emptyList()
) : UserFull, UserBasic(id, username, blockedAt), ) : UserFull,
UserBasic(id, username, blockedAt),
EntityCreatedAt by EntityCreatedAtImp(), EntityCreatedAt by EntityCreatedAtImp(),
EntityUpdatedAt by EntityUpdatedAtImp() EntityUpdatedAt by EntityUpdatedAtImp()

View File

@@ -12,4 +12,4 @@ fun UserI.makeToken(): String = JWT.create()
.withIssuer(JwtConfig.issuer) .withIssuer(JwtConfig.issuer)
.withClaim("id", id.toString()) .withClaim("id", id.toString())
.withExpiresAt(JwtConfig.getExpiration()) .withExpiresAt(JwtConfig.getExpiration())
.sign(JwtConfig.algorithm) .sign(JwtConfig.algorithm)

View File

@@ -22,4 +22,4 @@ object JwtConfig {
* Calculate the expiration Date based on current time + the given validity * Calculate the expiration Date based on current time + the given validity
*/ */
fun getExpiration() = Date(System.currentTimeMillis() + validityInMs) fun getExpiration() = Date(System.currentTimeMillis() + validityInMs)
} }

View File

@@ -40,4 +40,4 @@ fun jwtInstallation(userRepo: UserRepository): Authentication.Configuration.() -
} }
} }
} }
} }

View File

@@ -33,7 +33,8 @@ class CitizenRepository(override var requester: Requester) : RepositoryI {
): Paginated<CitizenBasic> = requester ): Paginated<CitizenBasic> = requester
.getFunction("find_citizens") .getFunction("find_citizens")
.select( .select(
page, limit, page,
limit,
"sort" to sort?.toSnakeCase(), "sort" to sort?.toSnakeCase(),
"direction" to direction, "direction" to direction,
"search" to search "search" to search

View File

@@ -23,4 +23,4 @@ class CitizenVoter : Voter() {
if (connectedCitizen == null) return denied("You must be connected to change your password", "citizen.changePassword.notConnected") if (connectedCitizen == null) return denied("You must be connected to change your password", "citizen.changePassword.notConnected")
return if (subject.id == connectedCitizen.id) granted() else denied("You can only change your password", "citizen.password.notYours") return if (subject.id == connectedCitizen.id) granted() else denied("You can only change your password", "citizen.password.notYours")
} }
} }

View File

@@ -42,4 +42,4 @@ fun Route.changeMyPassword(voter: CitizenVoter, userRepository: UserRepository)
call.respond(HttpStatusCode.BadRequest, "Request format is not correct") call.respond(HttpStatusCode.BadRequest, "Request format is not correct")
} }
} }
} }

View File

@@ -30,4 +30,4 @@ fun Route.findCitizen(voter: CitizenVoter, repo: CitizenRepository) {
voter.assert { canView(citizens.result, citizenOrNull) } voter.assert { canView(citizens.result, citizenOrNull) }
call.respond(citizens) call.respond(citizens)
} }
} }

View File

@@ -25,4 +25,4 @@ fun Route.getCurrentCitizen(voter: CitizenVoter) {
call.respond(citizen) call.respond(citizen)
} }
} }
} }

View File

@@ -20,4 +20,4 @@ fun Route.getOneCitizen(voter: CitizenVoter) {
call.respond(it.citizen) call.respond(it.citizen)
} }
} }

View File

@@ -27,7 +27,8 @@ class CommentArticleRepository(requester: Requester) : CommentRepositoryAbs<Arti
return requester.run { return requester.run {
getFunction("find_comments_by_citizen") getFunction("find_comments_by_citizen")
.select( .select(
page, limit, page,
limit,
"created_by_id" to citizen.id, "created_by_id" to citizen.id,
"reference" to TargetI.getReference(ArticleRef::class) "reference" to TargetI.getReference(ArticleRef::class)
) )
@@ -42,7 +43,8 @@ class CommentArticleRepository(requester: Requester) : CommentRepositoryAbs<Arti
): Paginated<CommentForView<ArticleForView, CitizenRef>> = requester ): Paginated<CommentForView<ArticleForView, CitizenRef>> = requester
.getFunction("find_comments_by_target") .getFunction("find_comments_by_target")
.select( .select(
page, limit, page,
limit,
"target_id" to target.id, "target_id" to target.id,
"sort" to sort.sql "sort" to sort.sql
) )
@@ -57,4 +59,4 @@ class CommentArticleRepository(requester: Requester) : CommentRepositoryAbs<Arti
} }
} }
} }
} }

View File

@@ -41,4 +41,4 @@ fun Route.createCommentArticle(repo: CommentArticleRepository, voter: CommentVot
call.respond(HttpStatusCode.Created, comment) call.respond(HttpStatusCode.Created, comment)
} }
} }
} }

View File

@@ -22,4 +22,4 @@ fun Route.getCitizenArticleComments(repo: CommentArticleRepository, voter: Comme
call.respond(comments) call.respond(comments)
} }
} }
} }

View File

@@ -36,7 +36,8 @@ abstract class CommentRepositoryAbs<T : TargetI>(override var requester: Request
return requester.run { return requester.run {
getFunction("find_comments_by_parent") getFunction("find_comments_by_parent")
.select( .select(
page, limit, page,
limit,
"parent_id" to parentId "parent_id" to parentId
) )
} }
@@ -60,7 +61,8 @@ abstract class CommentRepositoryAbs<T : TargetI>(override var requester: Request
return requester.run { return requester.run {
getFunction("find_comments_by_target") getFunction("find_comments_by_target")
.select( .select(
page, limit, page,
limit,
"target_id" to targetId, "target_id" to targetId,
"sort" to sort.sql "sort" to sort.sql
) )
@@ -101,7 +103,8 @@ class CommentRepository(requester: Requester) : CommentRepositoryAbs<TargetRef>(
return requester.run { return requester.run {
getFunction("find_comments_by_citizen") getFunction("find_comments_by_citizen")
.select( .select(
page, limit, page,
limit,
"created_by_id" to citizen.id "created_by_id" to citizen.id
) )
} }
@@ -115,7 +118,8 @@ class CommentRepository(requester: Requester) : CommentRepositoryAbs<TargetRef>(
return requester.run { return requester.run {
getFunction("find_comments_by_parent") getFunction("find_comments_by_parent")
.select( .select(
page, limit, page,
limit,
"parent_id" to parentId "parent_id" to parentId
) )
} }

View File

@@ -30,4 +30,4 @@ fun Route.editComment(repo: CommentRepository, voter: CommentVoter) {
call.respond(HttpStatusCode.OK, comment) call.respond(HttpStatusCode.OK, comment)
} }
} }

View File

@@ -39,4 +39,4 @@ fun Route.getChildrenComments(repo: CommentRepository, voter: CommentVoter) {
call.respond(HttpStatusCode.OK, comments) call.respond(HttpStatusCode.OK, comments)
} }
} }

View File

@@ -26,4 +26,4 @@ fun Route.getOneComment(repo: CommentRepository, voter: CommentVoter) {
call.respond(HttpStatusCode.OK, comment) call.respond(HttpStatusCode.OK, comment)
} }
} }

View File

@@ -9,7 +9,7 @@ object ConfigViews {
private fun waitElasticsearchIsUp(client: RestClient) { private fun waitElasticsearchIsUp(client: RestClient) {
val logger: Logger = LoggerFactory.getLogger("fr.dcproject.elasticsearch") val logger: Logger = LoggerFactory.getLogger("fr.dcproject.elasticsearch")
val request = Request("GET", "/_cluster/health") val request = Request("GET", "/_cluster/health")
repeat(5*60 / 2) { // 5 minutes repeat(5 * 60 / 2) { // 5 minutes
runCatching { runCatching {
client.performRequest(request).statusLine.statusCode client.performRequest(request).statusLine.statusCode
}.onSuccess { }.onSuccess {
@@ -74,7 +74,7 @@ object ConfigViews {
} }
} }
} }
""".trimIndent() """.trimIndent()
) )
}.let { }.let {
performRequest(it) performRequest(it)

View File

@@ -15,4 +15,4 @@ interface ViewManager <T> {
* Get Views aggregations * Get Views aggregations
*/ */
fun getViewsCount(entity: T): ViewAggregation fun getViewsCount(entity: T): ViewAggregation
} }

View File

@@ -104,4 +104,4 @@ fun <Z : CitizenWithUserI> List<Member<Z>>.getRoles(user: UserI): List<Role> =
fun <Z : CitizenWithUserI> List<Member<Z>>.getRoles(citizen: CitizenI): List<Role> = fun <Z : CitizenWithUserI> List<Member<Z>>.getRoles(citizen: CitizenI): List<Role> =
firstOrNull { it.citizen.id == citizen.id }?.roles ?: emptyList() firstOrNull { it.citizen.id == citizen.id }?.roles ?: emptyList()
interface WorkgroupI : UuidEntityI interface WorkgroupI : UuidEntityI

View File

@@ -30,7 +30,8 @@ class WorkgroupRepository(override var requester: Requester) : RepositoryI {
return requester return requester
.getFunction("find_workgroups") .getFunction("find_workgroups")
.select( .select(
page, limit, page,
limit,
"sort" to sort?.toSnakeCase(), "sort" to sort?.toSnakeCase(),
"direction" to direction, "direction" to direction,
"search" to search, "search" to search,
@@ -43,8 +44,8 @@ class WorkgroupRepository(override var requester: Requester) : RepositoryI {
.selectOne("resource" to workgroup) ?: error("query 'upsert_workgroup' return null") .selectOne("resource" to workgroup) ?: error("query 'upsert_workgroup' return null")
fun <W : WorkgroupRef> delete(workgroup: W) = requester fun <W : WorkgroupRef> delete(workgroup: W) = requester
.getFunction("delete_workgroup") .getFunction("delete_workgroup")
.perform("id" to workgroup.id) .perform("id" to workgroup.id)
fun addMember(workgroup: WorkgroupI, member: Member<CitizenI>): Member<CitizenBasic>? = fun addMember(workgroup: WorkgroupI, member: Member<CitizenI>): Member<CitizenBasic>? =
addMember(workgroup, member.citizen, member.roles) addMember(workgroup, member.citizen, member.roles)

View File

@@ -32,7 +32,12 @@ object GetWorkgroups {
fun Route.getWorkgroups(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.getWorkgroups(repo: WorkgroupRepository, voter: WorkgroupVoter) {
get<WorkgroupsRequest> { get<WorkgroupsRequest> {
val workgroups = val workgroups =
repo.find(it.page, it.limit, it.sort, it.direction, it.search, repo.find(
it.page,
it.limit,
it.sort,
it.direction,
it.search,
WorkgroupRepository.Filter(createdById = it.createdBy, members = it.members) WorkgroupRepository.Filter(createdById = it.createdBy, members = it.members)
) )
voter.assert { canView(workgroups.result, citizenOrNull) } voter.assert { canView(workgroups.result, citizenOrNull) }

View File

@@ -41,10 +41,10 @@ object DeleteMembersOfWorkgroup {
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
call.getMembersFromRequest().let { members -> call.getMembersFromRequest().let { members ->
voter.assert { canView(workgroup, citizenOrNull) } voter.assert { canView(workgroup, citizenOrNull) }
repo.removeMembers(workgroup, members) repo.removeMembers(workgroup, members)
}.let { members -> }.let { members ->
call.respond(HttpStatusCode.OK, members) call.respond(HttpStatusCode.OK, members)
} }
} ?: call.respond(HttpStatusCode.NotFound) } ?: call.respond(HttpStatusCode.NotFound)
} }
} }

View File

@@ -41,10 +41,10 @@ object UpdateMemberOfWorkgroup {
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
call.getMembersFromRequest().let { members -> call.getMembersFromRequest().let { members ->
voter.assert { canUpdateMembers(workgroup, citizenOrNull) } voter.assert { canUpdateMembers(workgroup, citizenOrNull) }
repo.updateMembers(workgroup, members) repo.updateMembers(workgroup, members)
}.let { members -> }.let { members ->
call.respond(HttpStatusCode.OK, members) call.respond(HttpStatusCode.OK, members)
} }
} ?: call.respond(HttpStatusCode.NotFound) } ?: call.respond(HttpStatusCode.NotFound)
} }
} }

View File

@@ -8,4 +8,4 @@ interface Opinionable {
class Imp(parent: fr.dcproject.entity.Opinionable) : Opinionable { class Imp(parent: fr.dcproject.entity.Opinionable) : Opinionable {
override val opinions: Opinions = parent.opinions override val opinions: Opinions = parent.opinions
} }
} }

View File

@@ -6,4 +6,4 @@ interface Votable {
class Imp(parent: fr.dcproject.entity.Votable) : Votable { class Imp(parent: fr.dcproject.entity.Votable) : Votable {
override val votes: VoteAggregation = VoteAggregation(parent) override val votes: VoteAggregation = VoteAggregation(parent)
} }
} }

View File

@@ -68,4 +68,4 @@ open class ConstitutionRef(id: UUID = UUID.randomUUID()) : ConstitutionS(id) {
) : UuidEntity(id) ) : UuidEntity(id)
} }
sealed class ConstitutionS(id: UUID = UUID.randomUUID()) : TargetRef(id), TargetI sealed class ConstitutionS(id: UUID = UUID.randomUUID()) : TargetRef(id), TargetI

View File

@@ -11,4 +11,4 @@ interface CreatedBy<T : CitizenI> : EntityCreatedBy<EntityI> {
override val createdBy: T override val createdBy: T
} }
class CreatedByImp<T : CitizenI>(override val createdBy: T) : CreatedBy<T> class CreatedByImp<T : CitizenI>(override val createdBy: T) : CreatedBy<T>

View File

@@ -5,4 +5,4 @@ import java.util.*
interface EntityI : EntityI { interface EntityI : EntityI {
val id: UUID val id: UUID
} }

View File

@@ -61,4 +61,4 @@ interface TargetI : UuidEntityI {
} }
val reference: String val reference: String
} }

View File

@@ -36,4 +36,4 @@ open class FollowRef(
override val id: UUID override val id: UUID
) : FollowI ) : FollowI
interface FollowI : UuidEntityI interface FollowI : UuidEntityI

View File

@@ -43,4 +43,4 @@ open class OpinionRef(
override val id: UUID override val id: UUID
) : OpinionI, TargetRef(id) ) : OpinionI, TargetRef(id)
interface OpinionI : UuidEntityI interface OpinionI : UuidEntityI

View File

@@ -13,4 +13,4 @@ class OpinionChoice(
open class OpinionChoiceRef( open class OpinionChoiceRef(
id: UUID? id: UUID?
) : UuidEntity(id ?: UUID.randomUUID()) ) : UuidEntity(id ?: UUID.randomUUID())

View File

@@ -9,4 +9,4 @@ interface Opinionable {
class OpinionableImp : Opinionable { class OpinionableImp : Opinionable {
override var opinions: OpinionsMutable = mutableMapOf() override var opinions: OpinionsMutable = mutableMapOf()
} }

View File

@@ -14,4 +14,4 @@ class VersionableRefImp(
interface Versionable : VersionableRef, EntityVersioning<UUID, Int> { interface Versionable : VersionableRef, EntityVersioning<UUID, Int> {
override val versionId: UUID override val versionId: UUID
override val versionNumber: Int override val versionNumber: Int
} }

View File

@@ -6,4 +6,4 @@ interface Votable {
class VotableImp : Votable { class VotableImp : Votable {
override val votes: VoteAggregation = VoteAggregation() override val votes: VoteAggregation = VoteAggregation()
} }

View File

@@ -13,12 +13,12 @@ import fr.dcproject.repository.Follow
import fr.postgresjson.serializer.deserialize import fr.postgresjson.serializer.deserialize
import io.ktor.application.* import io.ktor.application.*
import io.ktor.util.pipeline.* import io.ktor.util.pipeline.*
import io.ktor.utils.io.errors.*
import io.lettuce.core.api.async.RedisAsyncCommands import io.lettuce.core.api.async.RedisAsyncCommands
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.io.errors.IOException
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import fr.dcproject.repository.FollowArticle as FollowArticleRepository import fr.dcproject.repository.FollowArticle as FollowArticleRepository

View File

@@ -24,4 +24,4 @@ class Mailer(
throw ex throw ex
} }
} }
} }

View File

@@ -67,4 +67,4 @@ class NotificationEmailSender(
private fun noCitizen(id: UUID): Nothing = throw NoCitizen("No Citizen with this id : $id") private fun noCitizen(id: UUID): Nothing = throw NoCitizen("No Citizen with this id : $id")
private fun noTarget(id: UUID): Nothing = throw NoTarget("No Target with this id : $id") private fun noTarget(id: UUID): Nothing = throw NoTarget("No Target with this id : $id")
} }

View File

@@ -27,7 +27,8 @@ class CommentConstitutionRepository(requester: Requester) : CommentRepositoryAbs
return requester.run { return requester.run {
getFunction("find_comments_by_citizen") getFunction("find_comments_by_citizen")
.select( .select(
page, limit, page,
limit,
"created_by_id" to citizen.id, "created_by_id" to citizen.id,
"reference" to TargetI.getReference(ConstitutionRef::class) "reference" to TargetI.getReference(ConstitutionRef::class)
) )
@@ -43,7 +44,8 @@ class CommentConstitutionRepository(requester: Requester) : CommentRepositoryAbs
return requester.run { return requester.run {
getFunction("find_comments_by_target") getFunction("find_comments_by_target")
.select( .select(
page, limit, page,
limit,
"target_id" to target.id, "target_id" to target.id,
"sort" to sort.sql "sort" to sort.sql
) )

View File

@@ -27,7 +27,8 @@ class Constitution(override var requester: Requester) : RepositoryI {
return requester return requester
.getFunction("find_constitutions") .getFunction("find_constitutions")
.select( .select(
page, limit, page,
limit,
"sort" to sort?.toSnakeCase(), "sort" to sort?.toSnakeCase(),
"direction" to direction, "direction" to direction,
"search" to search "search" to search

View File

@@ -34,7 +34,8 @@ sealed class Follow<IN : TargetRef, OUT : TargetRef>(override var requester: Req
return requester return requester
.getFunction("find_follows_by_citizen") .getFunction("find_follows_by_citizen")
.select( .select(
page, limit, page,
limit,
"created_by_id" to citizenId "created_by_id" to citizenId
) )
} }
@@ -101,7 +102,8 @@ class FollowArticle(requester: Requester) : Follow<ArticleRef, ArticleForView>(r
return requester.run { return requester.run {
getFunction("find_follows_article_by_citizen") getFunction("find_follows_article_by_citizen")
.select( .select(
page, limit, page,
limit,
"created_by_id" to citizenId "created_by_id" to citizenId
) )
} }
@@ -115,7 +117,8 @@ class FollowArticle(requester: Requester) : Follow<ArticleRef, ArticleForView>(r
return requester return requester
.getFunction("find_follows_article_by_target") .getFunction("find_follows_article_by_target")
.select( .select(
page, limit, page,
limit,
"target_id" to target.id "target_id" to target.id
) )
} }
@@ -130,7 +133,8 @@ class FollowConstitution(requester: Requester) : Follow<ConstitutionRef, Constit
return requester.run { return requester.run {
getFunction("find_follows_constitution_by_citizen") getFunction("find_follows_constitution_by_citizen")
.select( .select(
page, limit, page,
limit,
"created_by_id" to citizenId "created_by_id" to citizenId
) )
} }

View File

@@ -82,7 +82,8 @@ abstract class Opinion<T : TargetRef>(requester: Requester) : OpinionChoice(requ
return requester.run { return requester.run {
getFunction("find_citizen_opinions_by_target_ids") getFunction("find_citizen_opinions_by_target_ids")
.select( .select(
typeReference, mapOf( typeReference,
mapOf(
"citizen_id" to citizen.id, "citizen_id" to citizen.id,
"ids" to targets "ids" to targets
) )
@@ -101,7 +102,8 @@ abstract class Opinion<T : TargetRef>(requester: Requester) : OpinionChoice(requ
return requester return requester
.getFunction("find_citizen_opinions_by_target_id") .getFunction("find_citizen_opinions_by_target_id")
.select( .select(
typeReference, mapOf( typeReference,
mapOf(
"citizen_id" to citizen.id, "citizen_id" to citizen.id,
"id" to target "id" to target
) )
@@ -121,7 +123,9 @@ abstract class Opinion<T : TargetRef>(requester: Requester) : OpinionChoice(requ
): Paginated<OpinionEntity<TargetRef>> { ): Paginated<OpinionEntity<TargetRef>> {
return requester return requester
.getFunction("find_citizen_opinions") .getFunction("find_citizen_opinions")
.select(page, limit, .select(
page,
limit,
"sort" to sort?.toSnakeCase(), "sort" to sort?.toSnakeCase(),
"direction" to direction, "direction" to direction,
"citizen_id" to citizen.id "citizen_id" to citizen.id
@@ -153,4 +157,4 @@ class OpinionArticle(requester: Requester) : Opinion<ArticleRef>(requester) {
.getFunction("upsert_opinion") .getFunction("upsert_opinion")
.selectOne("resource" to opinion)!! .selectOne("resource" to opinion)!!
} }
} }

View File

@@ -37,7 +37,10 @@ open class Vote<T : TargetI>(override var requester: Requester) : RepositoryI {
return requester.run { return requester.run {
getFunction("find_votes_by_citizen") getFunction("find_votes_by_citizen")
.select( .select(
page, limit, typeReference, mapOf( page,
limit,
typeReference,
mapOf(
"created_by_id" to citizenId, "created_by_id" to citizenId,
"reference" to target "reference" to target
) )
@@ -53,7 +56,8 @@ open class Vote<T : TargetI>(override var requester: Requester) : RepositoryI {
return requester.run { return requester.run {
getFunction("find_citizen_votes_by_target_ids") getFunction("find_citizen_votes_by_target_ids")
.select( .select(
typeReference, mapOf( typeReference,
mapOf(
"citizen_id" to citizen.id, "citizen_id" to citizen.id,
"ids" to targets "ids" to targets
) )
@@ -120,4 +124,4 @@ class VoteConstitution(requester: Requester) : Vote<Constitution>(requester) {
page, page,
limit limit
) )
} }

View File

@@ -50,4 +50,4 @@ fun Route.commentConstitution(repo: CommentConstitutionRepository, voter: Commen
voter.assert { canView(comments.result, citizenOrNull) } voter.assert { canView(comments.result, citizenOrNull) }
call.respond(comments) call.respond(comments)
} }
} }

View File

@@ -60,7 +60,10 @@ object ConstitutionPaths {
) : UuidEntity(id) { ) : UuidEntity(id) {
fun create(): TitleSimple<ArticleRef> = fun create(): TitleSimple<ArticleRef> =
TitleSimple( TitleSimple(
id, name, rank, articles id,
name,
rank,
articles
) )
} }
@@ -100,4 +103,4 @@ fun Route.constitution(repo: ConstitutionRepository, voter: ConstitutionVoter) {
call.respond(constitution) call.respond(constitution)
} }
} }
} }

View File

@@ -53,4 +53,4 @@ fun Route.followArticle(repo: FollowArticleRepository, voter: FollowVoter) {
} }
call.respond(follows) call.respond(follows)
} }
} }

View File

@@ -33,4 +33,4 @@ fun Route.opinionChoice(repo: OpinionChoiceRepository, voter: OpinionChoiceVoter
call.respond(opinionChoices) call.respond(opinionChoices)
} }
} }

View File

@@ -11,4 +11,4 @@ open class PaginatedRequest(
) : PaginatedRequestI { ) : PaginatedRequestI {
override val page: Int = if (page < 1) 1 else page override val page: Int = if (page < 1) 1 else page
override val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit override val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
} }

View File

@@ -88,4 +88,4 @@ fun Route.voteArticle(repo: VoteArticleRepository, voteCommentRepo: VoteComment,
} }
call.respond(votes) call.respond(votes)
} }
} }

View File

@@ -40,4 +40,4 @@ fun Route.voteConstitution(repo: VoteConstitutionRepository, voter: VoteVoter) {
repo.vote(vote) repo.vote(vote)
call.respond(HttpStatusCode.Created) call.respond(HttpStatusCode.Created)
} }
} }

View File

@@ -3,4 +3,4 @@ package fr.dcproject.utils
import org.joda.time.DateTime import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat import org.joda.time.format.ISODateTimeFormat
fun DateTime.toIso(): String = ISODateTimeFormat.dateTime().print(this) fun DateTime.toIso(): String = ISODateTimeFormat.dateTime().print(this)

View File

@@ -26,4 +26,4 @@ fun String.getJsonField(jsonPath: String): Int? {
.warn("No value for Json path ${JsonPath.compile(jsonPath).path}") .warn("No value for Json path ${JsonPath.compile(jsonPath).path}")
null null
} }
} }

View File

@@ -7,4 +7,4 @@ import kotlin.reflect.KProperty
internal class LoggerDelegate<in R : Any> : ReadOnlyProperty<R, Logger> { internal class LoggerDelegate<in R : Any> : ReadOnlyProperty<R, Logger> {
override fun getValue(thisRef: R, property: KProperty<*>): Logger = LoggerFactory.getLogger(thisRef.javaClass.packageName) override fun getValue(thisRef: R, property: KProperty<*>): Logger = LoggerFactory.getLogger(thisRef.javaClass.packageName)
} }

View File

@@ -4,4 +4,4 @@ fun String.readResource(callback: (String) -> Unit = {}): String {
val content = callback::class.java.getResource(this).readText() val content = callback::class.java.getResource(this).readText()
callback(content) callback(content)
return content return content
} }

View File

@@ -8,4 +8,4 @@ fun List<String?>.toUUID(): List<UUID> = this
.filterNotNull() .filterNotNull()
.map { it.trim() } .map { it.trim() }
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.map { UUID.fromString(it) } .map { UUID.fromString(it) }

View File

@@ -1,6 +1,7 @@
package steps package steps
import io.ktor.application.* import io.ktor.application.*
import io.ktor.server.engine.*
import io.ktor.server.testing.* import io.ktor.server.testing.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.test.fail import kotlin.test.fail

View File

@@ -6,11 +6,9 @@ import io.cucumber.java8.En
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.testing.* import io.ktor.server.testing.*
import io.ktor.util.* import io.ktor.util.*
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
@ImplicitReflectionSerializer
@KtorExperimentalAPI @KtorExperimentalAPI
class KtorServerRequestSteps : En { class KtorServerRequestSteps : En {
init { init {

View File

@@ -2,17 +2,11 @@ package steps
import io.cucumber.datatable.DataTable import io.cucumber.datatable.DataTable
import io.cucumber.java8.En import io.cucumber.java8.En
import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.json.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.parse
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
import kotlin.test.fail import kotlin.test.fail
@ImplicitReflectionSerializer
class KtorServerRestSteps : En { class KtorServerRestSteps : En {
init { init {
Then("the JSON should contain:") { dataTable: DataTable -> Then("the JSON should contain:") { dataTable: DataTable ->
@@ -60,7 +54,7 @@ class KtorServerRestSteps : En {
} }
private val responseJsonElement: JsonElement private val responseJsonElement: JsonElement
get() = Json.parse(KtorServerContext.defaultServer.call?.response?.content ?: fail("The response isn't valid JSON")) get() = Json.parseToJsonElement(KtorServerContext.defaultServer.call?.response?.content ?: fail("The response isn't valid JSON"))
private val response: String private val response: String
get() = KtorServerContext.defaultServer.call?.response?.content ?: fail("The response isn't valid") get() = KtorServerContext.defaultServer.call?.response?.content ?: fail("The response isn't valid")