From c9ce2a9dc78b6bb372a879d7f66911ab79f59f95 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Sat, 20 Mar 2021 00:55:39 +0100 Subject: [PATCH] Refactor constitution entity --- .../constitution/ConstitutionAccessControl.kt | 10 +-- .../constitution/database/Constitution.kt | 83 ++++++++--------- .../database/ConstitutionRepository.kt | 9 +- .../constitution/routes/CreateConstitution.kt | 90 +++++++++++++------ .../follow/database/FollowRepository.kt | 6 +- .../vote/database/VoteRepositoryAbs.kt | 8 +- .../constitution/find_constitutions.sql | 1 - .../integration/steps/given/Constitution.kt | 20 ++--- 8 files changed, 131 insertions(+), 96 deletions(-) diff --git a/src/main/kotlin/fr/dcproject/component/constitution/ConstitutionAccessControl.kt b/src/main/kotlin/fr/dcproject/component/constitution/ConstitutionAccessControl.kt index 38d8ce3..aa4f981 100644 --- a/src/main/kotlin/fr/dcproject/component/constitution/ConstitutionAccessControl.kt +++ b/src/main/kotlin/fr/dcproject/component/constitution/ConstitutionAccessControl.kt @@ -5,20 +5,20 @@ import fr.dcproject.common.entity.DeletedAt import fr.dcproject.common.security.AccessControl import fr.dcproject.common.security.AccessResponse import fr.dcproject.component.citizen.database.CitizenI +import fr.dcproject.component.constitution.database.ConstitutionForListing +import fr.dcproject.component.constitution.database.ConstitutionI import fr.dcproject.component.constitution.database.ConstitutionRef -import fr.dcproject.component.constitution.database.ConstitutionS -import fr.dcproject.component.constitution.database.ConstitutionSimple class ConstitutionAccessControl : AccessControl() { - fun canCreate(subject: ConstitutionS, citizen: CitizenI?): AccessResponse = when { + fun canCreate(subject: ConstitutionI, citizen: CitizenI?): AccessResponse = when { citizen == null -> denied("You must be connected to create constitution", "constitution.create.notConnected") else -> granted() } - fun > canView(subjects: List, citizen: CitizenI?): AccessResponse = + fun canView(subjects: List, citizen: CitizenI?): AccessResponse = canAll(subjects) { canView(it, citizen) } - fun canView(subject: S, citizen: CitizenI?): AccessResponse where S : DeletedAt, S : ConstitutionS = when { + fun canView(subject: S, citizen: CitizenI?): AccessResponse where S : DeletedAt, S : ConstitutionI = when { subject.isDeleted() -> denied("You cannot view a deleted constitution", "constitution.view.deleted") else -> granted() } diff --git a/src/main/kotlin/fr/dcproject/component/constitution/database/Constitution.kt b/src/main/kotlin/fr/dcproject/component/constitution/database/Constitution.kt index e0037b1..b16d970 100644 --- a/src/main/kotlin/fr/dcproject/component/constitution/database/Constitution.kt +++ b/src/main/kotlin/fr/dcproject/component/constitution/database/Constitution.kt @@ -4,6 +4,7 @@ import fr.dcproject.common.entity.CreatedAt import fr.dcproject.common.entity.CreatedBy import fr.dcproject.common.entity.DeletedAt import fr.dcproject.common.entity.Entity +import fr.dcproject.common.entity.EntityI import fr.dcproject.common.entity.TargetI import fr.dcproject.common.entity.TargetRef import fr.dcproject.common.entity.VersionableId @@ -11,68 +12,68 @@ import fr.dcproject.component.article.database.ArticleForListing import fr.dcproject.component.article.database.ArticleI import fr.dcproject.component.citizen.database.CitizenCreator import fr.dcproject.component.citizen.database.CitizenWithUserI -import fr.dcproject.component.constitution.database.ConstitutionSimple.TitleSimple +import fr.dcproject.component.constitution.database.ConstitutionForUpdate.TitleForUpdate import java.util.UUID -class Constitution( - id: UUID = UUID.randomUUID(), - title: String, - anonymous: Boolean = true, - titles: MutableList> = mutableListOf(), - draft: Boolean = false, - lastVersion: Boolean = false, - override val createdBy: CitizenCreator -) : ConstitutionSimple>( - id, - title = title, - anonymous = anonymous, - titles = titles, - draft = draft, - lastVersion = lastVersion, - createdBy = createdBy -) { - - class Title( - id: UUID = UUID.randomUUID(), - name: String, - rank: Int? = null, - override val articles: MutableList = mutableListOf() - ) : TitleSimple(id, name, rank) +data class ConstitutionForView( + override val id: UUID = UUID.randomUUID(), + val title: String, + val anonymous: Boolean = true, + val titles: List = listOf(), + val draft: Boolean = false, + val lastVersion: Boolean = false, + override val createdBy: CitizenCreator, + override val versionId: UUID = UUID.randomUUID(), +) : ConstitutionRef(id), + VersionableId, + CreatedAt by CreatedAt.Imp(), + CreatedBy, + DeletedAt by DeletedAt.Imp() { + data class TitleForView( + override val id: UUID = UUID.randomUUID(), + val name: String, + val rank: Int, + val articles: MutableList = mutableListOf(), + ) : TitleRef(id) } -open class ConstitutionSimple>( - id: UUID = UUID.randomUUID(), +data class ConstitutionForListing( + override val id: UUID = UUID.randomUUID(), + val title: String, + override val createdBy: CitizenCreator, + override val versionId: UUID = UUID.randomUUID(), +) : ConstitutionRef(id), + VersionableId, + CreatedAt by CreatedAt.Imp(), + CreatedBy, + DeletedAt by DeletedAt.Imp() + +data class ConstitutionForUpdate>( + override val id: UUID = UUID.randomUUID(), val title: String, val anonymous: Boolean = true, val titles: List = listOf(), val draft: Boolean = false, val lastVersion: Boolean = false, override val createdBy: Cr, - versionId: UUID = UUID.randomUUID() + override val versionId: UUID = UUID.randomUUID() ) : ConstitutionRef(id), VersionableId by VersionableId.Imp(versionId), CreatedAt by CreatedAt.Imp(), CreatedBy by CreatedBy.Imp(createdBy), DeletedAt by DeletedAt.Imp() { - init { - titles.forEachIndexed { index, title -> - title.rank = index - } - } - - open class TitleSimple( - id: UUID = UUID.randomUUID(), - var name: String, - var rank: Int? = null, - open val articles: MutableList = mutableListOf() + data class TitleForUpdate( + override val id: UUID = UUID.randomUUID(), + val name: String, + val articles: List = listOf() ) : TitleRef(id) } -open class ConstitutionRef(id: UUID? = null) : ConstitutionS(id ?: UUID.randomUUID()) { +open class ConstitutionRef(id: UUID? = null) : TargetRef(id), ConstitutionI { open class TitleRef( id: UUID = UUID.randomUUID() ) : Entity(id) } -sealed class ConstitutionS(id: UUID = UUID.randomUUID()) : TargetRef(id), TargetI +interface ConstitutionI : EntityI, TargetI diff --git a/src/main/kotlin/fr/dcproject/component/constitution/database/ConstitutionRepository.kt b/src/main/kotlin/fr/dcproject/component/constitution/database/ConstitutionRepository.kt index eb230f1..9f441f0 100644 --- a/src/main/kotlin/fr/dcproject/component/constitution/database/ConstitutionRepository.kt +++ b/src/main/kotlin/fr/dcproject/component/constitution/database/ConstitutionRepository.kt @@ -2,17 +2,16 @@ package fr.dcproject.component.constitution.database import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.citizen.database.CitizenWithUserI -import fr.dcproject.component.constitution.database.ConstitutionSimple.TitleSimple +import fr.dcproject.component.constitution.database.ConstitutionForUpdate.TitleForUpdate import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Requester import fr.postgresjson.repository.RepositoryI import fr.postgresjson.repository.RepositoryI.Direction import net.pearx.kasechange.toSnakeCase import java.util.UUID -import fr.dcproject.component.constitution.database.Constitution as ConstitutionEntity class ConstitutionRepository(override var requester: Requester) : RepositoryI { - fun findById(id: UUID): ConstitutionEntity? { + fun findById(id: UUID): ConstitutionForView? { val function = requester.getFunction("find_constitution_by_id") return function.selectOne("id" to id) } @@ -23,7 +22,7 @@ class ConstitutionRepository(override var requester: Requester) : RepositoryI { sort: String? = null, direction: Direction? = null, search: String? = null - ): Paginated { + ): Paginated { return requester .getFunction("find_constitutions") .select( @@ -35,7 +34,7 @@ class ConstitutionRepository(override var requester: Requester) : RepositoryI { ) } - fun upsert(constitution: ConstitutionSimple>): ConstitutionEntity? { + fun upsert(constitution: ConstitutionForUpdate>): ConstitutionForView? { return requester .getFunction("upsert_constitution") .selectOne("resource" to constitution) diff --git a/src/main/kotlin/fr/dcproject/component/constitution/routes/CreateConstitution.kt b/src/main/kotlin/fr/dcproject/component/constitution/routes/CreateConstitution.kt index 7d67339..a9b44a0 100644 --- a/src/main/kotlin/fr/dcproject/component/constitution/routes/CreateConstitution.kt +++ b/src/main/kotlin/fr/dcproject/component/constitution/routes/CreateConstitution.kt @@ -1,6 +1,5 @@ package fr.dcproject.component.constitution.routes -import fr.dcproject.common.entity.Entity import fr.dcproject.common.security.assert import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.component.article.database.ArticleRef @@ -9,12 +8,13 @@ import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.citizen.database.Citizen import fr.dcproject.component.citizen.database.CitizenWithUserI import fr.dcproject.component.constitution.ConstitutionAccessControl +import fr.dcproject.component.constitution.database.ConstitutionForUpdate +import fr.dcproject.component.constitution.database.ConstitutionForUpdate.TitleForUpdate import fr.dcproject.component.constitution.database.ConstitutionRepository -import fr.dcproject.component.constitution.database.ConstitutionSimple -import fr.dcproject.component.constitution.database.ConstitutionSimple.TitleSimple import fr.dcproject.component.constitution.routes.CreateConstitution.PostConstitutionRequest.Input import fr.dcproject.component.constitution.routes.CreateConstitution.PostConstitutionRequest.Input.Title import io.ktor.application.call +import io.ktor.http.HttpStatusCode import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.Location import io.ktor.locations.post @@ -27,30 +27,25 @@ object CreateConstitution { @Location("/constitutions") class PostConstitutionRequest { class Input( - var title: String, - var anonymous: Boolean = true, - var titles: MutableList = mutableListOf(), - var draft: Boolean = false, - var lastVersion: Boolean = false, - var versionId: UUID = UUID.randomUUID() + val title: String, + val anonymous: Boolean = true, + val titles: MutableList<Title> = mutableListOf(), + val draft: Boolean = false, + val versionId: UUID = UUID.randomUUID() ) { - init { - titles.forEachIndexed { index, title -> - title.rank = index - } - } class Title( - id: UUID = UUID.randomUUID(), - var name: String, - var rank: Int? = null, - var articles: MutableList<ArticleRef> = mutableListOf() - ) : Entity(id) + val id: UUID = UUID.randomUUID(), + val name: String, + val articles: List<ArticleRef> = listOf() + ) { + class ArticleRef(val id: UUID) + } } } private fun getNewConstitution(input: Input, citizen: Citizen) = input.run { - ConstitutionSimple<CitizenWithUserI, TitleSimple<ArticleRef>>( + ConstitutionForUpdate<CitizenWithUserI, TitleForUpdate<ArticleRef>>( id = UUID.randomUUID(), title = title, titles = titles.create(), @@ -59,23 +54,64 @@ object CreateConstitution { ) } - private fun List<Title>.create(): MutableList<TitleSimple<ArticleRef>> = + private fun List<Title>.create(): MutableList<TitleForUpdate<ArticleRef>> = map { it.create() }.toMutableList() - private fun Title.create(): TitleSimple<ArticleRef> = - TitleSimple( + private fun Title.create(): TitleForUpdate<ArticleRef> = + TitleForUpdate( id, name, - rank, - articles + articles.map { ArticleRef(it.id) } ) fun Route.createConstitution(repo: ConstitutionRepository, ac: ConstitutionAccessControl) { post<PostConstitutionRequest> { getNewConstitution(call.receiveOrBadRequest(), citizen).let { ac.assert { canCreate(it, citizenOrNull) } - repo.upsert(it) - call.respond(it) + val c = repo.upsert(it) ?: error("Unable to create Constitution") + call.respond( + HttpStatusCode.Created, + object { + val id: UUID = c.id + val title: String = c.title + val titles: List<Any> = c.titles.map { t -> + object { + val id: UUID = t.id + val name: String = t.name + val rank: Int = t.rank + val articles: List<Any> = t.articles.map { a -> + val id = a.id + val title = a.title + val createdBy = a.createdBy.let { cr -> + object { + val id: UUID = cr.id + val name: Any = cr.name.let { n -> + object { + val firstName: String = n.firstName + val lastName: String = n.lastName + } + } + val user: Any = cr.user.let { u -> + object { + val username: String = u.username + } + } + } + } + val workgroup: Any? = a.workgroup?.let { w -> + object { + val id = w.id + val name = w.name + } + } + } + } + } + val anonymous: Boolean = c.anonymous + val draft: Boolean = c.draft + val versionId: UUID = c.versionId + } + ) } } } diff --git a/src/main/kotlin/fr/dcproject/component/follow/database/FollowRepository.kt b/src/main/kotlin/fr/dcproject/component/follow/database/FollowRepository.kt index c8e2839..32d6623 100644 --- a/src/main/kotlin/fr/dcproject/component/follow/database/FollowRepository.kt +++ b/src/main/kotlin/fr/dcproject/component/follow/database/FollowRepository.kt @@ -5,6 +5,7 @@ import fr.dcproject.common.entity.TargetRef import fr.dcproject.component.article.database.ArticleForView import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.citizen.database.CitizenI +import fr.dcproject.component.constitution.database.ConstitutionForView import fr.dcproject.component.constitution.database.ConstitutionRef import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Requester @@ -12,7 +13,6 @@ import fr.postgresjson.repository.RepositoryI import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import java.util.UUID -import fr.dcproject.component.constitution.database.Constitution as ConstitutionEntity sealed class FollowRepository<IN : TargetRef, OUT : TargetRef>(override var requester: Requester) : RepositoryI { open fun findByCitizen( @@ -120,12 +120,12 @@ class FollowArticleRepository(requester: Requester) : FollowRepository<ArticleRe } } -class FollowConstitutionRepository(requester: Requester) : FollowRepository<ConstitutionRef, ConstitutionEntity>(requester) { +class FollowConstitutionRepository(requester: Requester) : FollowRepository<ConstitutionRef, ConstitutionForView>(requester) { override fun findByCitizen( citizenId: UUID, page: Int, limit: Int - ): Paginated<FollowForView<ConstitutionEntity>> { + ): Paginated<FollowForView<ConstitutionForView>> { return requester.run { getFunction("find_follows_constitution_by_citizen") .select( diff --git a/src/main/kotlin/fr/dcproject/component/vote/database/VoteRepositoryAbs.kt b/src/main/kotlin/fr/dcproject/component/vote/database/VoteRepositoryAbs.kt index 9dc6e31..8b1f678 100644 --- a/src/main/kotlin/fr/dcproject/component/vote/database/VoteRepositoryAbs.kt +++ b/src/main/kotlin/fr/dcproject/component/vote/database/VoteRepositoryAbs.kt @@ -7,7 +7,7 @@ import fr.dcproject.component.article.database.ArticleForView import fr.dcproject.component.citizen.database.CitizenCreatorI import fr.dcproject.component.citizen.database.CitizenI import fr.dcproject.component.comment.generic.database.CommentForView -import fr.dcproject.component.constitution.database.Constitution +import fr.dcproject.component.constitution.database.ConstitutionForView import fr.dcproject.component.vote.entity.VoteAggregation import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Requester @@ -116,16 +116,16 @@ class VoteCommentRepository(requester: Requester) : VoteRepositoryAbs<CommentFor ) } -class VoteConstitutionRepository(requester: Requester) : VoteRepositoryAbs<Constitution>(requester) { +class VoteConstitutionRepository(requester: Requester) : VoteRepositoryAbs<ConstitutionForView>(requester) { fun findByCitizen( citizen: CitizenEntity, page: Int = 1, limit: Int = 50 - ): Paginated<VoteEntity<Constitution>> = + ): Paginated<VoteEntity<ConstitutionForView>> = findByCitizen( citizen.id, "constitution", - object : TypeReference<List<VoteEntity<Constitution>>>() {}, + object : TypeReference<List<VoteEntity<ConstitutionForView>>>() {}, page, limit ) diff --git a/src/main/resources/sql/functions/constitution/find_constitutions.sql b/src/main/resources/sql/functions/constitution/find_constitutions.sql index b407d03..1dae8c0 100644 --- a/src/main/resources/sql/functions/constitution/find_constitutions.sql +++ b/src/main/resources/sql/functions/constitution/find_constitutions.sql @@ -15,7 +15,6 @@ begin select c.*, find_citizen_by_id_with_user(c.created_by_id) as created_by, - find_constitution_titles_by_id(c.id) as titles, zdb.score(c.ctid) _score from constitution as c where _search is null or _search = '' or c ==> dsl.multi_match('{title^3, intro}', _search) diff --git a/src/test/kotlin/integration/steps/given/Constitution.kt b/src/test/kotlin/integration/steps/given/Constitution.kt index b46ff4d..6227099 100644 --- a/src/test/kotlin/integration/steps/given/Constitution.kt +++ b/src/test/kotlin/integration/steps/given/Constitution.kt @@ -5,17 +5,17 @@ import fr.dcproject.common.utils.toUUID import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.citizen.database.CitizenI.Name import fr.dcproject.component.citizen.database.CitizenWithUserI -import fr.dcproject.component.constitution.database.Constitution +import fr.dcproject.component.constitution.database.ConstitutionForUpdate +import fr.dcproject.component.constitution.database.ConstitutionForUpdate.TitleForUpdate +import fr.dcproject.component.constitution.database.ConstitutionForView import fr.dcproject.component.constitution.database.ConstitutionRepository -import fr.dcproject.component.constitution.database.ConstitutionSimple -import fr.dcproject.component.constitution.database.ConstitutionSimple.TitleSimple import io.ktor.server.testing.TestApplicationEngine import org.koin.core.context.GlobalContext import java.util.UUID fun TestApplicationEngine.`Given I have constitution`( id: String? = null, - titles: List<TitleSimple<ArticleRef>>? = null, + titles: List<TitleForUpdate<ArticleRef>>? = null, createdBy: Name? = null ) { createConstitution(id?.toUUID(), titles, createdBy) @@ -29,26 +29,26 @@ fun TestApplicationEngine.`Given I have constitutions`( } } -fun createTitles(nbr: Int): List<TitleSimple<ArticleRef>> = sequence { +fun createTitles(nbr: Int): List<TitleForUpdate<ArticleRef>> = sequence { repeat(nbr) { yield(createTitle()) } }.toList() -fun createTitle(): TitleSimple<ArticleRef> { - return TitleSimple(name = LoremIpsum().getTitle(3)) +fun createTitle(): TitleForUpdate<ArticleRef> { + return TitleForUpdate(name = LoremIpsum().getTitle(3)) } fun createConstitution( id: UUID? = null, - titles: List<TitleSimple<ArticleRef>>? = null, + titles: List<TitleForUpdate<ArticleRef>>? = null, createdBy: Name? = null -): Constitution { +): ConstitutionForView { val constitutionRepository: ConstitutionRepository by lazy { GlobalContext.get().koin.get() } val creator: CitizenWithUserI = createCitizen(createdBy) - val constitution = ConstitutionSimple( + val constitution = ConstitutionForUpdate( id = id ?: UUID.randomUUID(), title = LoremIpsum().getTitle(3), titles = titles ?: createTitles(5),