Big refactoring #77

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

View File

@@ -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.editComment
import fr.dcproject.component.comment.generic.routes.getChildrenComments import fr.dcproject.component.comment.generic.routes.getChildrenComments
import fr.dcproject.component.comment.generic.routes.getOneComment 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.elasticsearch.configElasticIndexes
import fr.dcproject.event.EventNotification import fr.dcproject.event.EventNotification
import fr.dcproject.event.EventSubscriber import fr.dcproject.event.EventSubscriber
@@ -186,6 +195,16 @@ fun Application.module(env: Env = PROD) {
authLogin(get()) authLogin(get())
authRegister(get()) authRegister(get())
authSso(get()) authSso(get())
/* Workgroup */
getWorkgroups(get())
getWorkgroup(get())
createWorkgroup(get())
editWorkgroup(get())
deleteWorkgroup(get())
/* Workgroup members */
addMemberToWorkgroup(get())
deleteMemberOfWorkgroup(get())
updateMemberOfWorkgroup(get())
/* TODO */ /* TODO */
constitution(get()) constitution(get())
followArticle(get()) followArticle(get())
@@ -195,7 +214,6 @@ fun Application.module(env: Env = PROD) {
voteConstitution(get()) voteConstitution(get())
opinionArticle(get()) opinionArticle(get())
opinionChoice(get()) opinionChoice(get())
workgroup(get())
definition() definition()
} }

View File

@@ -9,9 +9,9 @@ import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.component.comment.generic.CommentRef import fr.dcproject.component.comment.generic.CommentRef
import fr.dcproject.entity.Constitution import fr.dcproject.entity.Constitution
import fr.dcproject.entity.ConstitutionRef 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.OpinionChoice
import fr.dcproject.repository.Workgroup import fr.dcproject.component.workgroup.WorkgroupRepository
import io.ktor.features.* import io.ktor.features.*
import io.ktor.util.* import io.ktor.util.*
import org.koin.core.context.GlobalContext import org.koin.core.context.GlobalContext
@@ -113,11 +113,11 @@ val converters: ConverterDeclaration = {
} }
} }
convert<fr.dcproject.entity.Workgroup<CitizenBasic>> { convert<fr.dcproject.component.workgroup.Workgroup<CitizenBasic>> {
decode { values, _ -> decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) } val id = values.singleOrNull()?.let { UUID.fromString(it) }
?: throw InternalError("Cannot convert $values to UUID") ?: throw InternalError("Cannot convert $values to UUID")
get<Workgroup>().findById(id) get<WorkgroupRepository>().findById(id)
?: throw NotFoundException("Workgroup $values not found") ?: throw NotFoundException("Workgroup $values not found")
} }
} }

View File

@@ -17,6 +17,7 @@ import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.component.citizen.CitizenVoter import fr.dcproject.component.citizen.CitizenVoter
import fr.dcproject.component.comment.article.CommentArticleRepository import fr.dcproject.component.comment.article.CommentArticleRepository
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.component.comment.generic.CommentVoter
import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.event.publisher.Publisher import fr.dcproject.event.publisher.Publisher
import fr.dcproject.messages.Mailer import fr.dcproject.messages.Mailer
import fr.dcproject.messages.NotificationEmailSender 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.VoteArticle as VoteArticleRepository
import fr.dcproject.repository.VoteComment as VoteCommentRepository import fr.dcproject.repository.VoteComment as VoteCommentRepository
import fr.dcproject.repository.VoteConstitution as VoteConstitutionRepository import fr.dcproject.repository.VoteConstitution as VoteConstitutionRepository
import fr.dcproject.repository.Workgroup as WorkgroupRepository
@KtorExperimentalAPI @KtorExperimentalAPI
val KoinModule = module { val KoinModule = module {

View File

@@ -1,5 +1,9 @@
package fr.dcproject.component.article 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.component.citizen.*
import fr.dcproject.entity.* import fr.dcproject.entity.*
import fr.postgresjson.entity.* import fr.postgresjson.entity.*

View File

@@ -6,17 +6,17 @@ import fr.dcproject.component.article.ArticleForUpdate
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRepository import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleVoter 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.ArticleUpdate
import fr.dcproject.event.raiseEvent import fr.dcproject.event.raiseEvent
import fr.dcproject.repository.Workgroup import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.voter.assert import fr.dcproject.voter.assert
import io.ktor.application.* import io.ktor.application.*
import io.ktor.locations.* import io.ktor.locations.*
import io.ktor.request.* import io.ktor.request.*
import io.ktor.response.* import io.ktor.response.*
import io.ktor.routing.* import io.ktor.routing.*
import io.ktor.util.pipeline.*
import java.util.* import java.util.*
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
@@ -36,8 +36,8 @@ class PostArticleRequest {
} }
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: Workgroup, voter: ArticleVoter) { fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: WorkgroupRepository, voter: ArticleVoter) {
suspend fun PipelineContext<Unit, ApplicationCall>.convertDtoToEntity(): ArticleForUpdate = call.receive<PostArticleRequest.Input>().run { suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receive<Input>().run {
ArticleForUpdate( ArticleForUpdate(
id = id ?: UUID.randomUUID(), id = id ?: UUID.randomUUID(),
title = title, title = title,
@@ -46,14 +46,14 @@ fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: Workgroup,
description = description, description = description,
tags = tags, tags = tags,
draft = draft, draft = draft,
createdBy = call.citizen, createdBy = citizen,
workgroup = if (workgroup != null) workgroupRepository.findById(workgroup.id) else null, workgroup = if (workgroup != null) workgroupRepository.findById(workgroup.id) else null,
versionId = versionId versionId = versionId
) )
} }
post<PostArticleRequest> { post<PostArticleRequest> {
val article = convertDtoToEntity() val article = call.convertRequestToEntity()
voter.assert { canUpsert(article, citizenOrNull) } voter.assert { canUpsert(article, citizenOrNull) }

View File

@@ -4,7 +4,7 @@ import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI import fr.dcproject.component.auth.UserI
import fr.dcproject.component.auth.UserRef import fr.dcproject.component.auth.UserRef
import fr.dcproject.component.citizen.CitizenI.Name import fr.dcproject.component.citizen.CitizenI.Name
import fr.dcproject.entity.WorkgroupSimple import fr.dcproject.component.workgroup.WorkgroupSimple
import fr.postgresjson.entity.* import fr.postgresjson.entity.*
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.* import java.util.*

View File

@@ -1,11 +1,11 @@
package fr.dcproject.entity package fr.dcproject.component.workgroup
import fr.dcproject.component.auth.UserI import fr.dcproject.component.auth.UserI
import fr.dcproject.component.citizen.CitizenBasicI import fr.dcproject.component.citizen.CitizenBasicI
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenWithUserI import fr.dcproject.component.citizen.CitizenWithUserI
import fr.dcproject.entity.WorkgroupWithMembersI.Member import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member
import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member.Role
import fr.postgresjson.entity.* import fr.postgresjson.entity.*
import fr.postgresjson.entity.EntityI import fr.postgresjson.entity.EntityI
import java.util.* import java.util.*

View File

@@ -1,12 +1,8 @@
package fr.dcproject.repository package fr.dcproject.component.workgroup
import fr.dcproject.component.citizen.CitizenBasic import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.WorkgroupI import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member
import fr.dcproject.entity.WorkgroupRef
import fr.dcproject.entity.WorkgroupSimple
import fr.dcproject.entity.WorkgroupWithMembersI
import fr.dcproject.entity.WorkgroupWithMembersI.Member
import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.Parameter import fr.postgresjson.entity.Parameter
@@ -15,9 +11,9 @@ import fr.postgresjson.repository.RepositoryI.Direction
import fr.postgresjson.serializer.serialize import fr.postgresjson.serializer.serialize
import net.pearx.kasechange.toSnakeCase import net.pearx.kasechange.toSnakeCase
import java.util.* 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<CitizenBasic>? { fun findById(id: UUID): WorkgroupEntity<CitizenBasic>? {
val function = requester.getFunction("find_workgroup_by_id") val function = requester.getFunction("find_workgroup_by_id")
return function.selectOne("id" to id) return function.selectOne("id" to id)

View File

@@ -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<PostWorkgroupRequest> {
call.receive<Input>().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)
}
}
}
}

View File

@@ -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<DeleteWorkgroupRequest> {
repo.findById(it.workgroupId)?.let { workgroup ->
assertCan(WorkgroupVoter.Action.DELETE, workgroup)
repo.delete(workgroup)
call.respond(HttpStatusCode.NoContent)
} ?: call.respond(HttpStatusCode.NotFound)
}
}
}

View File

@@ -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<PutWorkgroupRequest> {
repo.findById(it.workgroupId)?.let { old ->
call.receive<Input>().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)
}
}
}

View File

@@ -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<WorkgroupRequest> {
repo.findById(it.workgroupId)?.let { workgroup ->
assertCan(WorkgroupVoter.Action.VIEW, workgroup)
call.respond(workgroup)
} ?: call.respond(HttpStatusCode.NotFound)
}
}
}

View File

@@ -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<String?>? = 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<UUID>? = members?.toUUID()
}
fun Route.getWorkgroups(repo: WorkgroupRepository) {
get<WorkgroupsRequest> {
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)
}
}
}

View File

@@ -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<Input.Item> by mutableListOf() {
class Item(val citizen: CitizenRef, roles: List<String> = emptyList()) {
val roles: List<WorkgroupWithMembersI.Member.Role> = roles.map {
WorkgroupWithMembersI.Member.Role.valueOf(it)
}
}
}
}
@KtorExperimentalLocationsAPI
private suspend fun ApplicationCall.getMembersFromRequest(): List<WorkgroupWithMembersI.Member<CitizenRef>> = receive<WorkgroupsMembersRequest.Input>().map {
WorkgroupWithMembersI.Member(
citizen = it.citizen,
roles = it.roles
)
}
@KtorExperimentalLocationsAPI
fun Route.addMemberToWorkgroup(repo: WorkgroupRepository) {
/* Add members to workgroup */
post<WorkgroupsMembersRequest> {
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)
}
}
}

View File

@@ -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<Input.Item> by mutableListOf() {
class Item(val citizen: CitizenRef, roles: List<String> = emptyList()) {
val roles: List<WorkgroupWithMembersI.Member.Role> = roles.map {
WorkgroupWithMembersI.Member.Role.valueOf(it)
}
}
}
}
private suspend fun ApplicationCall.getMembersFromRequest(): List<WorkgroupWithMembersI.Member<CitizenRef>> = receive<WorkgroupsMembersRequest.Input>().map {
WorkgroupWithMembersI.Member(
citizen = it.citizen,
roles = it.roles
)
}
fun Route.deleteMemberOfWorkgroup(repo: WorkgroupRepository) {
/* Delete members of workgroup */
delete<WorkgroupsMembersRequest> {
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)
}
}
}

View File

@@ -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<Input.Item> by mutableListOf() {
class Item(val citizen: CitizenRef, roles: List<String> = emptyList()) {
val roles: List<WorkgroupWithMembersI.Member.Role> = roles.map {
WorkgroupWithMembersI.Member.Role.valueOf(it)
}
}
}
}
private suspend fun ApplicationCall.getMembersFromRequest(): List<WorkgroupWithMembersI.Member<CitizenRef>> = receive<WorkgroupsMembersRequest.Input>().map {
WorkgroupWithMembersI.Member(
citizen = it.citizen,
roles = it.roles
)
}
fun Route.updateMemberOfWorkgroup(repo: WorkgroupRepository) {
/* Update members of workgroup */
put<WorkgroupsMembersRequest> {
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)
}
}
}

View File

@@ -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<String?>? = 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<UUID>? = 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<CitizenRef> = call.receive<Body>().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<Body.Item> by mutableListOf() {
class Item(val citizen: CitizenRef, roles: List<String> = emptyList()) {
val roles: List<Role> = roles.map {
Role.valueOf(it)
}
}
}
suspend fun getMembers(call: ApplicationCall): List<Member<CitizenRef>> = call.receive<Body>().map {
Member(
citizen = it.citizen,
roles = it.roles
)
}
}
}
@KtorExperimentalLocationsAPI
fun Route.workgroup(repo: WorkgroupRepo) {
get<WorkgroupsPaths.WorkgroupsRequest> {
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<WorkgroupsPaths.WorkgroupRequest> {
assertCan(VIEW, it.workgroup)
call.respond(it.workgroup)
}
post<WorkgroupsPaths.PostWorkgroupRequest> {
it.getNewWorkgroup(call)
.let { workgroup ->
assertCan(CREATE, workgroup)
repo.upsert(workgroup)
}.let {
call.respond(HttpStatusCode.Created, it)
}
}
put<WorkgroupsPaths.PutWorkgroupRequest> {
repo.findById(it.workgroupId)?.let { old ->
call.receive<Input>().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<WorkgroupsPaths.DeleteWorkgroupRequest> {
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<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
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<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
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<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
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)
}
}
}

View File

@@ -1,9 +1,9 @@
package fr.dcproject.security.voter package fr.dcproject.security.voter
import fr.dcproject.component.auth.UserI import fr.dcproject.component.auth.UserI
import fr.dcproject.entity.WorkgroupI import fr.dcproject.component.workgroup.WorkgroupI
import fr.dcproject.entity.WorkgroupWithAuthI import fr.dcproject.component.workgroup.WorkgroupWithAuthI
import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member.Role
import fr.dcproject.user import fr.dcproject.user
import fr.dcproject.voter.NoRuleDefinedException import fr.dcproject.voter.NoRuleDefinedException
import fr.dcproject.voter.NoSubjectDefinedException import fr.dcproject.voter.NoSubjectDefinedException

View File

@@ -8,7 +8,7 @@ import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenRepository import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.component.comment.article.CommentArticleRepository import fr.dcproject.component.comment.article.CommentArticleRepository
import fr.dcproject.component.comment.generic.CommentForUpdate import fr.dcproject.component.comment.generic.CommentForUpdate
import fr.dcproject.entity.WorkgroupRef import fr.dcproject.component.workgroup.WorkgroupRef
import fr.dcproject.utils.toUUID import fr.dcproject.utils.toUUID
import io.cucumber.datatable.DataTable import io.cucumber.datatable.DataTable
import io.cucumber.java8.En import io.cucumber.java8.En

View File

@@ -1,12 +1,14 @@
package steps package steps
import fr.dcproject.component.workgroup.Workgroup
import fr.dcproject.component.workgroup.WorkgroupRef
import fr.dcproject.component.auth.User import fr.dcproject.component.auth.User
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenRef import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.component.citizen.CitizenRepository import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.entity.* import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.entity.WorkgroupWithMembersI.Member import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member
import fr.dcproject.utils.toUUID import fr.dcproject.utils.toUUID
import io.cucumber.datatable.DataTable import io.cucumber.datatable.DataTable
import io.cucumber.java8.En import io.cucumber.java8.En
@@ -15,7 +17,6 @@ import org.junit.Assert
import org.koin.test.KoinTest import org.koin.test.KoinTest
import org.koin.test.get import org.koin.test.get
import java.util.* import java.util.*
import fr.dcproject.repository.Workgroup as WorkgroupRepository
class WorkgroupSteps : En, KoinTest { class WorkgroupSteps : En, KoinTest {
init { init {

View File

@@ -6,8 +6,8 @@ import fr.dcproject.component.auth.UserI
import fr.dcproject.component.citizen.CitizenBasic import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.WorkgroupRef import fr.dcproject.component.workgroup.WorkgroupRef
import fr.dcproject.entity.WorkgroupWithMembersI import fr.dcproject.component.workgroup.WorkgroupWithMembersI
import fr.dcproject.security.voter.WorkgroupVoter import fr.dcproject.security.voter.WorkgroupVoter
import fr.dcproject.user import fr.dcproject.user
import fr.dcproject.voter.NoSubjectDefinedException 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.Execution
import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
import java.util.* import java.util.*
import fr.dcproject.entity.Workgroup as WorkgroupEntity import fr.dcproject.component.workgroup.Workgroup as WorkgroupEntity
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)