From ecda29abe577855d74c305f1bd5b0f0c51bcf4a4 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Sun, 17 Jan 2021 14:18:19 +0100 Subject: [PATCH] Move Workgroup to a component --- src/main/kotlin/Application.kt | 20 +- src/main/kotlin/Converters.kt | 8 +- src/main/kotlin/KoinModule.kt | 2 +- src/main/kotlin/component/article/Article.kt | 4 + .../component/article/routes/UpsertArticle.kt | 14 +- src/main/kotlin/component/citizen/Citizen.kt | 2 +- .../workgroup}/Workgroup.kt | 6 +- .../workgroup/WorkgroupRepository.kt} | 12 +- .../workgroup/routes/CreateWorkgroup.kt | 49 ++++ .../workgroup/routes/DeleteWorkgroup.kt | 27 +++ .../workgroup/routes/EditWorkgroup.kt | 46 ++++ .../workgroup/routes/GetWorkgroup.kt | 26 +++ .../workgroup/routes/GetWorkgroups.kt | 41 ++++ .../routes/members/AddMemberToWorkgroup.kt | 52 +++++ .../members/DeleteMembersOfWorkgroup.kt | 50 +++++ .../routes/members/UpdateMemberOfWorkgroup.kt | 50 +++++ src/main/kotlin/routes/Workgroup.kt | 212 ------------------ src/main/kotlin/voter/WorkgroupVoter.kt | 6 +- src/test/kotlin/steps/ArticleSteps.kt | 2 +- src/test/kotlin/steps/WorkgroupSteps.kt | 7 +- .../kotlin/unit/voter/WorkgroupVoterTest.kt | 6 +- 21 files changed, 395 insertions(+), 247 deletions(-) rename src/main/kotlin/{entity => component/workgroup}/Workgroup.kt (95%) rename src/main/kotlin/{repository/Workgroup.kt => component/workgroup/WorkgroupRepository.kt} (89%) create mode 100644 src/main/kotlin/component/workgroup/routes/CreateWorkgroup.kt create mode 100644 src/main/kotlin/component/workgroup/routes/DeleteWorkgroup.kt create mode 100644 src/main/kotlin/component/workgroup/routes/EditWorkgroup.kt create mode 100644 src/main/kotlin/component/workgroup/routes/GetWorkgroup.kt create mode 100644 src/main/kotlin/component/workgroup/routes/GetWorkgroups.kt create mode 100644 src/main/kotlin/component/workgroup/routes/members/AddMemberToWorkgroup.kt create mode 100644 src/main/kotlin/component/workgroup/routes/members/DeleteMembersOfWorkgroup.kt create mode 100644 src/main/kotlin/component/workgroup/routes/members/UpdateMemberOfWorkgroup.kt delete mode 100644 src/main/kotlin/routes/Workgroup.kt diff --git a/src/main/kotlin/Application.kt b/src/main/kotlin/Application.kt index b3ea880..9af5eb4 100644 --- a/src/main/kotlin/Application.kt +++ b/src/main/kotlin/Application.kt @@ -28,6 +28,15 @@ import fr.dcproject.component.comment.generic.routes.createCommentChildren import fr.dcproject.component.comment.generic.routes.editComment import fr.dcproject.component.comment.generic.routes.getChildrenComments import fr.dcproject.component.comment.generic.routes.getOneComment +import fr.dcproject.component.workgroup.routes.* +import fr.dcproject.component.workgroup.routes.CreateWorkgroup.createWorkgroup +import fr.dcproject.component.workgroup.routes.DeleteWorkgroup.deleteWorkgroup +import fr.dcproject.component.workgroup.routes.EditWorkgroup.editWorkgroup +import fr.dcproject.component.workgroup.routes.GetWorkgroup.getWorkgroup +import fr.dcproject.component.workgroup.routes.GetWorkgroups.getWorkgroups +import fr.dcproject.component.workgroup.routes.members.AddMemberToWorkgroup.addMemberToWorkgroup +import fr.dcproject.component.workgroup.routes.members.DeleteMembersOfWorkgroup.deleteMemberOfWorkgroup +import fr.dcproject.component.workgroup.routes.members.UpdateMemberOfWorkgroup.updateMemberOfWorkgroup import fr.dcproject.elasticsearch.configElasticIndexes import fr.dcproject.event.EventNotification import fr.dcproject.event.EventSubscriber @@ -186,6 +195,16 @@ fun Application.module(env: Env = PROD) { authLogin(get()) authRegister(get()) authSso(get()) + /* Workgroup */ + getWorkgroups(get()) + getWorkgroup(get()) + createWorkgroup(get()) + editWorkgroup(get()) + deleteWorkgroup(get()) + /* Workgroup members */ + addMemberToWorkgroup(get()) + deleteMemberOfWorkgroup(get()) + updateMemberOfWorkgroup(get()) /* TODO */ constitution(get()) followArticle(get()) @@ -195,7 +214,6 @@ fun Application.module(env: Env = PROD) { voteConstitution(get()) opinionArticle(get()) opinionChoice(get()) - workgroup(get()) definition() } diff --git a/src/main/kotlin/Converters.kt b/src/main/kotlin/Converters.kt index d78ce7c..bb7dedf 100644 --- a/src/main/kotlin/Converters.kt +++ b/src/main/kotlin/Converters.kt @@ -9,9 +9,9 @@ import fr.dcproject.component.citizen.CitizenRef import fr.dcproject.component.comment.generic.CommentRef import fr.dcproject.entity.Constitution import fr.dcproject.entity.ConstitutionRef -import fr.dcproject.entity.WorkgroupRef +import fr.dcproject.component.workgroup.WorkgroupRef import fr.dcproject.repository.OpinionChoice -import fr.dcproject.repository.Workgroup +import fr.dcproject.component.workgroup.WorkgroupRepository import io.ktor.features.* import io.ktor.util.* import org.koin.core.context.GlobalContext @@ -113,11 +113,11 @@ val converters: ConverterDeclaration = { } } - convert> { + convert> { decode { values, _ -> val id = values.singleOrNull()?.let { UUID.fromString(it) } ?: throw InternalError("Cannot convert $values to UUID") - get().findById(id) + get().findById(id) ?: throw NotFoundException("Workgroup $values not found") } } diff --git a/src/main/kotlin/KoinModule.kt b/src/main/kotlin/KoinModule.kt index cc656b0..b7c0b83 100644 --- a/src/main/kotlin/KoinModule.kt +++ b/src/main/kotlin/KoinModule.kt @@ -17,6 +17,7 @@ import fr.dcproject.component.citizen.CitizenRepository import fr.dcproject.component.citizen.CitizenVoter import fr.dcproject.component.comment.article.CommentArticleRepository import fr.dcproject.component.comment.generic.CommentVoter +import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.event.publisher.Publisher import fr.dcproject.messages.Mailer import fr.dcproject.messages.NotificationEmailSender @@ -42,7 +43,6 @@ import fr.dcproject.repository.OpinionChoice as OpinionChoiceRepository import fr.dcproject.repository.VoteArticle as VoteArticleRepository import fr.dcproject.repository.VoteComment as VoteCommentRepository import fr.dcproject.repository.VoteConstitution as VoteConstitutionRepository -import fr.dcproject.repository.Workgroup as WorkgroupRepository @KtorExperimentalAPI val KoinModule = module { diff --git a/src/main/kotlin/component/article/Article.kt b/src/main/kotlin/component/article/Article.kt index 92dc64e..39c56d0 100644 --- a/src/main/kotlin/component/article/Article.kt +++ b/src/main/kotlin/component/article/Article.kt @@ -1,5 +1,9 @@ package fr.dcproject.component.article +import fr.dcproject.component.workgroup.WorkgroupCart +import fr.dcproject.component.workgroup.WorkgroupCartI +import fr.dcproject.component.workgroup.WorkgroupRef +import fr.dcproject.component.workgroup.WorkgroupSimple import fr.dcproject.component.citizen.* import fr.dcproject.entity.* import fr.postgresjson.entity.* diff --git a/src/main/kotlin/component/article/routes/UpsertArticle.kt b/src/main/kotlin/component/article/routes/UpsertArticle.kt index 40eefa2..e525b9a 100644 --- a/src/main/kotlin/component/article/routes/UpsertArticle.kt +++ b/src/main/kotlin/component/article/routes/UpsertArticle.kt @@ -6,17 +6,17 @@ import fr.dcproject.component.article.ArticleForUpdate import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleRepository import fr.dcproject.component.article.ArticleVoter -import fr.dcproject.entity.WorkgroupRef +import fr.dcproject.component.article.routes.PostArticleRequest.Input +import fr.dcproject.component.workgroup.WorkgroupRef import fr.dcproject.event.ArticleUpdate import fr.dcproject.event.raiseEvent -import fr.dcproject.repository.Workgroup +import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.voter.assert import io.ktor.application.* import io.ktor.locations.* import io.ktor.request.* import io.ktor.response.* import io.ktor.routing.* -import io.ktor.util.pipeline.* import java.util.* @KtorExperimentalLocationsAPI @@ -36,8 +36,8 @@ class PostArticleRequest { } @KtorExperimentalLocationsAPI -fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: Workgroup, voter: ArticleVoter) { - suspend fun PipelineContext.convertDtoToEntity(): ArticleForUpdate = call.receive().run { +fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: WorkgroupRepository, voter: ArticleVoter) { + suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receive().run { ArticleForUpdate( id = id ?: UUID.randomUUID(), title = title, @@ -46,14 +46,14 @@ fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: Workgroup, description = description, tags = tags, draft = draft, - createdBy = call.citizen, + createdBy = citizen, workgroup = if (workgroup != null) workgroupRepository.findById(workgroup.id) else null, versionId = versionId ) } post { - val article = convertDtoToEntity() + val article = call.convertRequestToEntity() voter.assert { canUpsert(article, citizenOrNull) } diff --git a/src/main/kotlin/component/citizen/Citizen.kt b/src/main/kotlin/component/citizen/Citizen.kt index f8b116b..e22cad0 100644 --- a/src/main/kotlin/component/citizen/Citizen.kt +++ b/src/main/kotlin/component/citizen/Citizen.kt @@ -4,7 +4,7 @@ import fr.dcproject.component.auth.User import fr.dcproject.component.auth.UserI import fr.dcproject.component.auth.UserRef import fr.dcproject.component.citizen.CitizenI.Name -import fr.dcproject.entity.WorkgroupSimple +import fr.dcproject.component.workgroup.WorkgroupSimple import fr.postgresjson.entity.* import org.joda.time.DateTime import java.util.* diff --git a/src/main/kotlin/entity/Workgroup.kt b/src/main/kotlin/component/workgroup/Workgroup.kt similarity index 95% rename from src/main/kotlin/entity/Workgroup.kt rename to src/main/kotlin/component/workgroup/Workgroup.kt index 8f16500..57b43a9 100644 --- a/src/main/kotlin/entity/Workgroup.kt +++ b/src/main/kotlin/component/workgroup/Workgroup.kt @@ -1,11 +1,11 @@ -package fr.dcproject.entity +package fr.dcproject.component.workgroup import fr.dcproject.component.auth.UserI import fr.dcproject.component.citizen.CitizenBasicI import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenWithUserI -import fr.dcproject.entity.WorkgroupWithMembersI.Member -import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role +import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member +import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member.Role import fr.postgresjson.entity.* import fr.postgresjson.entity.EntityI import java.util.* diff --git a/src/main/kotlin/repository/Workgroup.kt b/src/main/kotlin/component/workgroup/WorkgroupRepository.kt similarity index 89% rename from src/main/kotlin/repository/Workgroup.kt rename to src/main/kotlin/component/workgroup/WorkgroupRepository.kt index 466060c..3847980 100644 --- a/src/main/kotlin/repository/Workgroup.kt +++ b/src/main/kotlin/component/workgroup/WorkgroupRepository.kt @@ -1,12 +1,8 @@ -package fr.dcproject.repository +package fr.dcproject.component.workgroup import fr.dcproject.component.citizen.CitizenBasic import fr.dcproject.component.citizen.CitizenI -import fr.dcproject.entity.WorkgroupI -import fr.dcproject.entity.WorkgroupRef -import fr.dcproject.entity.WorkgroupSimple -import fr.dcproject.entity.WorkgroupWithMembersI -import fr.dcproject.entity.WorkgroupWithMembersI.Member +import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Requester import fr.postgresjson.entity.Parameter @@ -15,9 +11,9 @@ import fr.postgresjson.repository.RepositoryI.Direction import fr.postgresjson.serializer.serialize import net.pearx.kasechange.toSnakeCase import java.util.* -import fr.dcproject.entity.Workgroup as WorkgroupEntity +import fr.dcproject.component.workgroup.Workgroup as WorkgroupEntity -class Workgroup(override var requester: Requester) : RepositoryI { +class WorkgroupRepository(override var requester: Requester) : RepositoryI { fun findById(id: UUID): WorkgroupEntity? { val function = requester.getFunction("find_workgroup_by_id") return function.selectOne("id" to id) diff --git a/src/main/kotlin/component/workgroup/routes/CreateWorkgroup.kt b/src/main/kotlin/component/workgroup/routes/CreateWorkgroup.kt new file mode 100644 index 0000000..2044998 --- /dev/null +++ b/src/main/kotlin/component/workgroup/routes/CreateWorkgroup.kt @@ -0,0 +1,49 @@ +package fr.dcproject.component.workgroup.routes + +import fr.dcproject.citizen +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.component.workgroup.WorkgroupSimple +import fr.dcproject.component.workgroup.routes.CreateWorkgroup.PostWorkgroupRequest.Input +import fr.dcproject.security.voter.WorkgroupVoter +import fr.ktorVoter.assertCan +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.locations.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import java.util.* + +@KtorExperimentalLocationsAPI +object CreateWorkgroup { + @Location("/workgroups") + open class PostWorkgroupRequest { + class Input( + val id: UUID?, + val name: String, + val description: String, + val logo: String?, + val anonymous: Boolean? + ) + } + + fun Route.createWorkgroup(repo: WorkgroupRepository) { + post { + call.receive().run { + WorkgroupSimple( + id ?: UUID.randomUUID(), + name, + description, + logo, + anonymous ?: true, + citizen + ) + }.let { workgroup -> + assertCan(WorkgroupVoter.Action.CREATE, workgroup) + repo.upsert(workgroup) + }.let { + call.respond(HttpStatusCode.Created, it) + } + } + } +} diff --git a/src/main/kotlin/component/workgroup/routes/DeleteWorkgroup.kt b/src/main/kotlin/component/workgroup/routes/DeleteWorkgroup.kt new file mode 100644 index 0000000..653055a --- /dev/null +++ b/src/main/kotlin/component/workgroup/routes/DeleteWorkgroup.kt @@ -0,0 +1,27 @@ +package fr.dcproject.component.workgroup.routes + +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.security.voter.WorkgroupVoter +import fr.ktorVoter.assertCan +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.locations.* +import io.ktor.response.* +import io.ktor.routing.* +import java.util.* + +@KtorExperimentalLocationsAPI +object DeleteWorkgroup { + @Location("/workgroups/{workgroupId}") + class DeleteWorkgroupRequest(val workgroupId: UUID) + + fun Route.deleteWorkgroup(repo: WorkgroupRepository) { + delete { + repo.findById(it.workgroupId)?.let { workgroup -> + assertCan(WorkgroupVoter.Action.DELETE, workgroup) + repo.delete(workgroup) + call.respond(HttpStatusCode.NoContent) + } ?: call.respond(HttpStatusCode.NotFound) + } + } +} diff --git a/src/main/kotlin/component/workgroup/routes/EditWorkgroup.kt b/src/main/kotlin/component/workgroup/routes/EditWorkgroup.kt new file mode 100644 index 0000000..17da336 --- /dev/null +++ b/src/main/kotlin/component/workgroup/routes/EditWorkgroup.kt @@ -0,0 +1,46 @@ +package fr.dcproject.component.workgroup.routes + +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.component.workgroup.routes.EditWorkgroup.PutWorkgroupRequest.Input +import fr.dcproject.security.voter.WorkgroupVoter +import fr.ktorVoter.assertCan +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.locations.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import org.koin.core.KoinComponent +import java.util.* + +@KtorExperimentalLocationsAPI +object EditWorkgroup { + @Location("/workgroups/{workgroupId}") + class PutWorkgroupRequest(val workgroupId: UUID) : KoinComponent { + class Input( + val name: String?, + val description: String?, + val logo: String?, + val anonymous: Boolean? + ) + } + + fun Route.editWorkgroup(repo: WorkgroupRepository) { + put { + repo.findById(it.workgroupId)?.let { old -> + call.receive().run { + old.copy( + name = name ?: old.name, + description = description ?: old.description, + logo = logo ?: old.logo, + anonymous = anonymous ?: old.anonymous + ).let { workgroup -> + assertCan(WorkgroupVoter.Action.UPDATE, workgroup) + repo.upsert(workgroup) + call.respond(HttpStatusCode.OK, it) + } + } + } ?: call.respond(HttpStatusCode.NotFound) + } + } +} diff --git a/src/main/kotlin/component/workgroup/routes/GetWorkgroup.kt b/src/main/kotlin/component/workgroup/routes/GetWorkgroup.kt new file mode 100644 index 0000000..9252784 --- /dev/null +++ b/src/main/kotlin/component/workgroup/routes/GetWorkgroup.kt @@ -0,0 +1,26 @@ +package fr.dcproject.component.workgroup.routes + +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.security.voter.WorkgroupVoter +import fr.ktorVoter.assertCan +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.locations.* +import io.ktor.response.* +import io.ktor.routing.* +import java.util.* + +@KtorExperimentalLocationsAPI +object GetWorkgroup { + @Location("/workgroups/{workgroupId}") + class WorkgroupRequest(val workgroupId: UUID) + + fun Route.getWorkgroup(repo: WorkgroupRepository) { + get { + repo.findById(it.workgroupId)?.let { workgroup -> + assertCan(WorkgroupVoter.Action.VIEW, workgroup) + call.respond(workgroup) + } ?: call.respond(HttpStatusCode.NotFound) + } + } +} diff --git a/src/main/kotlin/component/workgroup/routes/GetWorkgroups.kt b/src/main/kotlin/component/workgroup/routes/GetWorkgroups.kt new file mode 100644 index 0000000..474b252 --- /dev/null +++ b/src/main/kotlin/component/workgroup/routes/GetWorkgroups.kt @@ -0,0 +1,41 @@ +package fr.dcproject.component.workgroup.routes + +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.security.voter.WorkgroupVoter +import fr.dcproject.utils.toUUID +import fr.ktorVoter.assertCanAll +import fr.postgresjson.repository.RepositoryI +import io.ktor.application.* +import io.ktor.locations.* +import io.ktor.response.* +import io.ktor.routing.* +import java.util.* + +@KtorExperimentalLocationsAPI +object GetWorkgroups { + @Location("/workgroups") + class WorkgroupsRequest( + page: Int = 1, + limit: Int = 50, + val sort: String? = null, + val direction: RepositoryI.Direction? = null, + val search: String? = null, + val createdBy: String? = null, + members: List? = null + ) { + val page: Int = if (page < 1) 1 else page + val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit + val members: List? = members?.toUUID() + } + + fun Route.getWorkgroups(repo: WorkgroupRepository) { + get { + val workgroups = + repo.find(it.page, it.limit, it.sort, it.direction, it.search, + WorkgroupRepository.Filter(createdById = it.createdBy, members = it.members) + ) + assertCanAll(WorkgroupVoter.Action.VIEW, workgroups.result) + call.respond(workgroups) + } + } +} diff --git a/src/main/kotlin/component/workgroup/routes/members/AddMemberToWorkgroup.kt b/src/main/kotlin/component/workgroup/routes/members/AddMemberToWorkgroup.kt new file mode 100644 index 0000000..a727804 --- /dev/null +++ b/src/main/kotlin/component/workgroup/routes/members/AddMemberToWorkgroup.kt @@ -0,0 +1,52 @@ +package fr.dcproject.component.workgroup.routes.members + +import fr.dcproject.component.citizen.CitizenRef +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.component.workgroup.WorkgroupWithMembersI +import fr.dcproject.security.voter.WorkgroupVoter +import fr.ktorVoter.assertCan +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.locations.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import org.koin.core.KoinComponent +import java.util.* + +@KtorExperimentalLocationsAPI +object AddMemberToWorkgroup { + @Location("/workgroups/{workgroupId}/members") + class WorkgroupsMembersRequest(val workgroupId: UUID) : KoinComponent { + class Input : MutableList by mutableListOf() { + class Item(val citizen: CitizenRef, roles: List = emptyList()) { + val roles: List = roles.map { + WorkgroupWithMembersI.Member.Role.valueOf(it) + } + } + } + } + + @KtorExperimentalLocationsAPI + private suspend fun ApplicationCall.getMembersFromRequest(): List> = receive().map { + WorkgroupWithMembersI.Member( + citizen = it.citizen, + roles = it.roles + ) + } + + @KtorExperimentalLocationsAPI + fun Route.addMemberToWorkgroup(repo: WorkgroupRepository) { + /* Add members to workgroup */ + post { + repo.findById(it.workgroupId)?.let { workgroup -> + call.getMembersFromRequest().let { members -> + assertCan(WorkgroupVoter.ActionMembers.ADD, workgroup) + repo.addMembers(workgroup, members) + }.let { members -> + call.respond(HttpStatusCode.Created, members) + } + } ?: call.respond(HttpStatusCode.NotFound) + } + } +} diff --git a/src/main/kotlin/component/workgroup/routes/members/DeleteMembersOfWorkgroup.kt b/src/main/kotlin/component/workgroup/routes/members/DeleteMembersOfWorkgroup.kt new file mode 100644 index 0000000..417b2f7 --- /dev/null +++ b/src/main/kotlin/component/workgroup/routes/members/DeleteMembersOfWorkgroup.kt @@ -0,0 +1,50 @@ +package fr.dcproject.component.workgroup.routes.members + +import fr.dcproject.component.citizen.CitizenRef +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.component.workgroup.WorkgroupWithMembersI +import fr.dcproject.security.voter.WorkgroupVoter +import fr.ktorVoter.assertCan +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.locations.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import org.koin.core.KoinComponent +import java.util.* + +@KtorExperimentalLocationsAPI +object DeleteMembersOfWorkgroup { + @Location("/workgroups/{workgroupId}/members") + class WorkgroupsMembersRequest(val workgroupId: UUID) : KoinComponent { + class Input : MutableList by mutableListOf() { + class Item(val citizen: CitizenRef, roles: List = emptyList()) { + val roles: List = roles.map { + WorkgroupWithMembersI.Member.Role.valueOf(it) + } + } + } + } + + private suspend fun ApplicationCall.getMembersFromRequest(): List> = receive().map { + WorkgroupWithMembersI.Member( + citizen = it.citizen, + roles = it.roles + ) + } + + fun Route.deleteMemberOfWorkgroup(repo: WorkgroupRepository) { + /* Delete members of workgroup */ + delete { + repo.findById(it.workgroupId)?.let { workgroup -> + call.getMembersFromRequest().let { members -> + assertCan(WorkgroupVoter.ActionMembers.REMOVE, workgroup) + repo.removeMembers(workgroup, members) + }.let { members -> + call.respond(HttpStatusCode.OK, members) + } + } ?: call.respond(HttpStatusCode.NotFound) + } + } +} diff --git a/src/main/kotlin/component/workgroup/routes/members/UpdateMemberOfWorkgroup.kt b/src/main/kotlin/component/workgroup/routes/members/UpdateMemberOfWorkgroup.kt new file mode 100644 index 0000000..5629612 --- /dev/null +++ b/src/main/kotlin/component/workgroup/routes/members/UpdateMemberOfWorkgroup.kt @@ -0,0 +1,50 @@ +package fr.dcproject.component.workgroup.routes.members + +import fr.dcproject.component.citizen.CitizenRef +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.component.workgroup.WorkgroupWithMembersI +import fr.dcproject.security.voter.WorkgroupVoter +import fr.ktorVoter.assertCan +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.locations.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import org.koin.core.KoinComponent +import java.util.* + +@KtorExperimentalLocationsAPI +object UpdateMemberOfWorkgroup { + @Location("/workgroups/{workgroupId}/members") + class WorkgroupsMembersRequest(val workgroupId: UUID) : KoinComponent { + class Input : MutableList by mutableListOf() { + class Item(val citizen: CitizenRef, roles: List = emptyList()) { + val roles: List = roles.map { + WorkgroupWithMembersI.Member.Role.valueOf(it) + } + } + } + } + + private suspend fun ApplicationCall.getMembersFromRequest(): List> = receive().map { + WorkgroupWithMembersI.Member( + citizen = it.citizen, + roles = it.roles + ) + } + + fun Route.updateMemberOfWorkgroup(repo: WorkgroupRepository) { + /* Update members of workgroup */ + put { + repo.findById(it.workgroupId)?.let { workgroup -> + call.getMembersFromRequest().let { members -> + assertCan(WorkgroupVoter.ActionMembers.UPDATE, workgroup) + repo.updateMembers(workgroup, members) + }.let { members -> + call.respond(HttpStatusCode.OK, members) + } + } ?: call.respond(HttpStatusCode.NotFound) + } + } +} diff --git a/src/main/kotlin/routes/Workgroup.kt b/src/main/kotlin/routes/Workgroup.kt deleted file mode 100644 index 3cc128e..0000000 --- a/src/main/kotlin/routes/Workgroup.kt +++ /dev/null @@ -1,212 +0,0 @@ -package fr.dcproject.routes - -import fr.dcproject.citizen -import fr.dcproject.component.citizen.CitizenRef -import fr.dcproject.entity.WorkgroupSimple -import fr.dcproject.entity.WorkgroupWithMembersI.Member -import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role -import fr.dcproject.repository.Workgroup.Filter -import fr.dcproject.routes.WorkgroupsPaths.PutWorkgroupRequest.Input -import fr.dcproject.security.voter.WorkgroupVoter.Action.* -import fr.dcproject.security.voter.WorkgroupVoter.Action.UPDATE -import fr.dcproject.utils.toUUID -import fr.ktorVoter.assertCan -import fr.ktorVoter.assertCanAll -import fr.postgresjson.repository.RepositoryI -import io.ktor.application.* -import io.ktor.http.* -import io.ktor.locations.* -import io.ktor.request.* -import io.ktor.response.* -import io.ktor.routing.* -import org.koin.core.KoinComponent -import org.koin.core.inject -import java.util.* -import fr.dcproject.repository.Workgroup as WorkgroupRepo -import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.ADD as ADD_MEMBERS -import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.REMOVE as REMOVE_MEMBERS -import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.UPDATE as UPDATE_MEMBERS - -@KtorExperimentalLocationsAPI -object WorkgroupsPaths { - @Location("/workgroups") - class WorkgroupsRequest( - page: Int = 1, - limit: Int = 50, - val sort: String? = null, - val direction: RepositoryI.Direction? = null, - val search: String? = null, - val createdBy: String? = null, - members: List? = null - ) { - val page: Int = if (page < 1) 1 else page - val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit - val members: List? = members?.toUUID() - } - - @Location("/workgroups/{workgroupId}") - class WorkgroupRequest(private val workgroupId: UUID) : KoinComponent { - val repo: WorkgroupRepo by inject() - val workgroup = repo.findById(workgroupId) ?: TODO() - } - - @Location("/workgroups") - open class PostWorkgroupRequest { - class Body( - val id: UUID?, - val name: String, - val description: String, - val logo: String?, - val anonymous: Boolean? - ) - - suspend fun getNewWorkgroup(call: ApplicationCall): WorkgroupSimple = call.receive().run { - WorkgroupSimple( - id ?: UUID.randomUUID(), - name, - description, - logo, - anonymous ?: true, - call.citizen - ) - } - } - - @Location("/workgroups/{workgroupId}") - class PutWorkgroupRequest(val workgroupId: UUID) : KoinComponent { - class Input( - val name: String?, - val description: String?, - val logo: String?, - val anonymous: Boolean? - ) - } - - @Location("/workgroups/{workgroupId}") - class DeleteWorkgroupRequest(val workgroupId: UUID) : KoinComponent { - val repo: WorkgroupRepo by inject() - val workgroup = repo.findById(workgroupId) - } -} - -@KtorExperimentalLocationsAPI -object WorkgroupsMembersPaths { - @Location("/workgroups/{workgroupId}/members") - class WorkgroupsMembersRequest(val workgroupId: UUID) : KoinComponent { - val repo: WorkgroupRepo by inject() - val workgroup = repo.findById(workgroupId) - - class Body : MutableList by mutableListOf() { - class Item(val citizen: CitizenRef, roles: List = emptyList()) { - val roles: List = roles.map { - Role.valueOf(it) - } - } - } - - suspend fun getMembers(call: ApplicationCall): List> = call.receive().map { - Member( - citizen = it.citizen, - roles = it.roles - ) - } - } -} - -@KtorExperimentalLocationsAPI -fun Route.workgroup(repo: WorkgroupRepo) { - get { - val workgroups = - repo.find(it.page, it.limit, it.sort, it.direction, it.search, Filter(createdById = it.createdBy, members = it.members)) - assertCanAll(VIEW, workgroups.result) - call.respond(workgroups) - } - - get { - assertCan(VIEW, it.workgroup) - - call.respond(it.workgroup) - } - - post { - it.getNewWorkgroup(call) - .let { workgroup -> - assertCan(CREATE, workgroup) - repo.upsert(workgroup) - }.let { - call.respond(HttpStatusCode.Created, it) - } - } - - put { - repo.findById(it.workgroupId)?.let { old -> - call.receive().run { - old.copy( - name = name ?: old.name, - description = description ?: old.description, - logo = logo ?: old.logo, - anonymous = anonymous ?: old.anonymous - ).let { workgroup -> - assertCan(UPDATE, workgroup) - repo.upsert(workgroup) - call.respond(HttpStatusCode.OK, it) - } - } - } ?: call.respond(HttpStatusCode.NotFound) - } - - delete { - if (it.workgroup != null) { - assertCan(DELETE, it.workgroup) - repo.delete(it.workgroup) - call.respond(HttpStatusCode.NoContent) - } else { - call.respond(HttpStatusCode.NotFound) - } - } - - /* Add members to workgroup */ - post { - if (it.workgroup != null) { - it.getMembers(call) - .let { members -> - assertCan(ADD_MEMBERS, it.workgroup) - repo.addMembers(it.workgroup, members) - }.let { members -> - call.respond(HttpStatusCode.Created, members) - } - } else { - call.respond(HttpStatusCode.NotFound) - } - } - - /* Delete members of workgroup */ - delete { - if (it.workgroup != null) { - it.getMembers(call) - .let { members -> - assertCan(REMOVE_MEMBERS, it.workgroup) - repo.removeMembers(it.workgroup, members) - }.let { members -> - call.respond(HttpStatusCode.OK, members) - } - } else { - call.respond(HttpStatusCode.NotFound) - } - } - - /* Update members of workgroup */ - put { - if (it.workgroup != null) { - it.getMembers(call) - .let { members -> - assertCan(UPDATE_MEMBERS, it.workgroup) - repo.updateMembers(it.workgroup, members) - }.let { members -> - call.respond(HttpStatusCode.OK, members) - } - } else { - call.respond(HttpStatusCode.NotFound) - } - } -} diff --git a/src/main/kotlin/voter/WorkgroupVoter.kt b/src/main/kotlin/voter/WorkgroupVoter.kt index 4b96c45..f213770 100644 --- a/src/main/kotlin/voter/WorkgroupVoter.kt +++ b/src/main/kotlin/voter/WorkgroupVoter.kt @@ -1,9 +1,9 @@ package fr.dcproject.security.voter import fr.dcproject.component.auth.UserI -import fr.dcproject.entity.WorkgroupI -import fr.dcproject.entity.WorkgroupWithAuthI -import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role +import fr.dcproject.component.workgroup.WorkgroupI +import fr.dcproject.component.workgroup.WorkgroupWithAuthI +import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member.Role import fr.dcproject.user import fr.dcproject.voter.NoRuleDefinedException import fr.dcproject.voter.NoSubjectDefinedException diff --git a/src/test/kotlin/steps/ArticleSteps.kt b/src/test/kotlin/steps/ArticleSteps.kt index d4d2efe..f7a0cc4 100644 --- a/src/test/kotlin/steps/ArticleSteps.kt +++ b/src/test/kotlin/steps/ArticleSteps.kt @@ -8,7 +8,7 @@ import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenRepository import fr.dcproject.component.comment.article.CommentArticleRepository import fr.dcproject.component.comment.generic.CommentForUpdate -import fr.dcproject.entity.WorkgroupRef +import fr.dcproject.component.workgroup.WorkgroupRef import fr.dcproject.utils.toUUID import io.cucumber.datatable.DataTable import io.cucumber.java8.En diff --git a/src/test/kotlin/steps/WorkgroupSteps.kt b/src/test/kotlin/steps/WorkgroupSteps.kt index d650bd0..302c831 100644 --- a/src/test/kotlin/steps/WorkgroupSteps.kt +++ b/src/test/kotlin/steps/WorkgroupSteps.kt @@ -1,12 +1,14 @@ package steps +import fr.dcproject.component.workgroup.Workgroup +import fr.dcproject.component.workgroup.WorkgroupRef import fr.dcproject.component.auth.User import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenRef import fr.dcproject.component.citizen.CitizenRepository -import fr.dcproject.entity.* -import fr.dcproject.entity.WorkgroupWithMembersI.Member +import fr.dcproject.component.workgroup.WorkgroupRepository +import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member import fr.dcproject.utils.toUUID import io.cucumber.datatable.DataTable import io.cucumber.java8.En @@ -15,7 +17,6 @@ import org.junit.Assert import org.koin.test.KoinTest import org.koin.test.get import java.util.* -import fr.dcproject.repository.Workgroup as WorkgroupRepository class WorkgroupSteps : En, KoinTest { init { diff --git a/src/test/kotlin/unit/voter/WorkgroupVoterTest.kt b/src/test/kotlin/unit/voter/WorkgroupVoterTest.kt index cc0440e..6c45cae 100644 --- a/src/test/kotlin/unit/voter/WorkgroupVoterTest.kt +++ b/src/test/kotlin/unit/voter/WorkgroupVoterTest.kt @@ -6,8 +6,8 @@ import fr.dcproject.component.auth.UserI import fr.dcproject.component.citizen.CitizenBasic import fr.dcproject.component.citizen.CitizenCart import fr.dcproject.component.citizen.CitizenI -import fr.dcproject.entity.WorkgroupRef -import fr.dcproject.entity.WorkgroupWithMembersI +import fr.dcproject.component.workgroup.WorkgroupRef +import fr.dcproject.component.workgroup.WorkgroupWithMembersI import fr.dcproject.security.voter.WorkgroupVoter import fr.dcproject.user import fr.dcproject.voter.NoSubjectDefinedException @@ -25,7 +25,7 @@ import org.junit.jupiter.api.* import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT import java.util.* -import fr.dcproject.entity.Workgroup as WorkgroupEntity +import fr.dcproject.component.workgroup.Workgroup as WorkgroupEntity @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Execution(CONCURRENT)