#29 Implement Workgroup members routes (Add, remove, update)

This commit is contained in:
2020-03-15 20:13:10 +01:00
parent f277613820
commit 8ad0281003
8 changed files with 195 additions and 17 deletions

View File

@@ -151,10 +151,10 @@ fun Application.module(env: Env = PROD) {
}
}
convert<CitizenRef> {
convert<WorkgroupRef> {
decode { values, _ ->
values.singleOrNull()?.let {
CitizenRef(UUID.fromString(it))
WorkgroupRef(UUID.fromString(it))
} ?: throw NotFoundException("""UUID "$values" is not valid for Workgroup""")
}
}
@@ -288,7 +288,6 @@ fun Application.module(env: Env = PROD) {
install(AutoHeadResponse)
install(ContentNegotiation) {
// TODO move to postgresJson lib
jackson {
propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE

View File

@@ -43,10 +43,9 @@ open class WorkgroupRef(
id: UUID?
) : UuidEntity(id ?: UUID.randomUUID()), WorkgroupI
interface WorkgroupWithAuthI<Z : CitizenWithUserI> : WorkgroupI, EntityCreatedBy<Z>, EntityDeletedAt {
interface WorkgroupWithAuthI<Z : CitizenWithUserI> : WorkgroupWithMembersI<Z>, EntityCreatedBy<Z>, EntityDeletedAt {
val anonymous: Boolean
val owner: Z
var members: List<Z>
fun isMember(user: UserI): Boolean =
members.map { it.user.id }.contains(user.id) || owner.user.id == user.id
@@ -55,4 +54,8 @@ interface WorkgroupWithAuthI<Z : CitizenWithUserI> : WorkgroupI, EntityCreatedBy
isMember(citizen.user)
}
interface WorkgroupWithMembersI<Z : CitizenI> : WorkgroupI {
var members: List<Z>
}
interface WorkgroupI : UuidEntityI

View File

@@ -1,12 +1,12 @@
package fr.dcproject.repository
import fr.dcproject.entity.CitizenRef
import fr.dcproject.entity.WorkgroupSimple
import fr.dcproject.entity.*
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.Parameter
import fr.postgresjson.repository.RepositoryI
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
@@ -40,6 +40,41 @@ class Workgroup(override var requester: Requester) : RepositoryI {
.getFunction("upsert_workgroup")
.selectOne("resource" to workgroup) ?: error("query 'upsert_workgroup' return null")
fun addMember(workgroup: WorkgroupI, member: CitizenI): List<CitizenBasic> =
addMembers(workgroup, listOf(member))
fun addMembers(workgroup: WorkgroupI, members: List<CitizenI>): List<CitizenBasic> = requester
.getFunction("add_workgroup_members")
.select(
"id" to workgroup.id,
"resource" to members.serialize()
)
fun removeMember(workgroup: WorkgroupI, memberToDelete: CitizenI): List<CitizenBasic> =
removeMembers(workgroup, listOf(memberToDelete))
fun removeMembers(workgroup: WorkgroupI, membersToDelete: List<CitizenI>): List<CitizenBasic> = requester
.getFunction("remove_workgroup_members")
.select(
"id" to workgroup.id,
"resource" to membersToDelete
)
fun updateMembers(workgroup: WorkgroupI, members: List<CitizenI>): List<CitizenBasic> = requester
.getFunction("update_workgroup_members")
.select(
"id" to workgroup.id,
"resource" to members
)
fun <W : WorkgroupWithMembersI<CitizenI>> updateMembers(workgroup: W): W {
updateMembers(workgroup, workgroup.members).let {
workgroup.members = it
}
return workgroup
}
class Filter(
val createdById: String? = null
) : Parameter

View File

@@ -8,7 +8,9 @@ import fr.dcproject.entity.request.getContent
import fr.dcproject.repository.Workgroup.Filter
import fr.dcproject.security.voter.WorkgroupVoter.Action.VIEW
import fr.dcproject.security.voter.WorkgroupVoter.Action.CREATE
import fr.dcproject.security.voter.WorkgroupVoter.Action.UPDATE
import fr.dcproject.security.voter.assertCan
import fr.dcproject.utils.toUUID
import fr.postgresjson.repository.RepositoryI
import io.ktor.application.ApplicationCall
import io.ktor.application.call
@@ -17,6 +19,8 @@ import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
import io.ktor.locations.get
import io.ktor.locations.post
import io.ktor.locations.put
import io.ktor.locations.delete
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
@@ -72,6 +76,22 @@ object WorkgroupsPaths {
}
}
@KtorExperimentalLocationsAPI
object WorkgroupsMembersPaths {
@Location("/workgroups/members/{workgroup}")
class WorkgroupsMembersRequest(val workgroup: WorkgroupEntity) : RequestBuilder<List<CitizenRef>> {
class Content : MutableList<Content.Item> by mutableListOf() {
class Item(val id: String)
}
override suspend fun getContent(call: ApplicationCall): List<CitizenRef> {
return call.receive<Content>().map {
CitizenRef(it.id.toUUID())
}
}
}
}
@KtorExperimentalLocationsAPI
fun Route.workgroup(repo: WorkgroupRepository) {
get<WorkgroupsPaths.WorkgroupsRequest> {
@@ -96,4 +116,37 @@ fun Route.workgroup(repo: WorkgroupRepository) {
call.respond(HttpStatusCode.Created, it)
}
}
/* Add members to workgroup */
post<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
call.getContent(it)
.let { members ->
assertCan(UPDATE, it.workgroup)
repo.addMembers(it.workgroup, members)
}.let {
call.respond(HttpStatusCode.OK, it)
}
}
/* Delete members of workgroup */
delete<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
call.getContent(it)
.let { members ->
assertCan(UPDATE, it.workgroup)
repo.removeMembers(it.workgroup, members)
}.let {
call.respond(HttpStatusCode.OK, it)
}
}
/* Update members of workgroup */
put<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
call.getContent(it)
.let { members ->
assertCan(UPDATE, it.workgroup)
repo.updateMembers(it.workgroup, members)
}.let {
call.respond(HttpStatusCode.OK, it)
}
}
}

View File

@@ -11,6 +11,7 @@ import io.ktor.server.testing.setBody
import io.ktor.util.KtorExperimentalAPI
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
@ImplicitReflectionSerializer
@KtorExperimentalAPI
@@ -50,6 +51,12 @@ class KtorServerRequestSteps : En {
}
}
Then("the response should not contain object:") { expected: DataTable ->
expected.asMap<String, String>(String::class.java, String::class.java).forEach { (key, valueExpected) ->
assertNotEquals(valueExpected, JsonPath.read<Any>(response, key)?.toString() ?: throw AssertionError("\"$key\" element not found on json response"))
}
}
Then("print last response") {
print(KtorServerContext.defaultServer.call?.response?.content)
}

View File

@@ -1,6 +1,7 @@
package feature
import fr.dcproject.entity.*
import fr.dcproject.utils.toUUID
import io.cucumber.datatable.DataTable
import io.cucumber.java8.En
import org.joda.time.DateTime
@@ -12,12 +13,19 @@ import fr.dcproject.repository.Workgroup as WorkgroupRepository
class WorkgroupSteps : En, KoinTest {
init {
When("I have members in workgroup {string}:") { workgroupId: String, members: DataTable ->
val membersRefs = members.asList()
.map { CitizenRef(it.toUUID()) }
get<WorkgroupRepository>().addMembers(WorkgroupRef(workgroupId.toUUID()), membersRefs)
}
When("I have workgroup:") { body: DataTable ->
val data = body.asMap<String, String>(String::class.java, String::class.java)
val creator = if (data["created_by"] != null) {
CitizenRef(UUID.fromString(data["created_by"]))
} else {
val creator = data["created_by"]?.let {
get<CitizenRepository>().findByUsername(it.toLowerCase().replace(' ', '-'))
} ?: kotlin.run {
val username = "paul-langevin".toLowerCase() + UUID.randomUUID()
val user = User(
username = username,
@@ -32,13 +40,12 @@ class WorkgroupSteps : En, KoinTest {
get<CitizenRepository>().insertWithUser(it)
}
}
val owner = if (data["owner"] != null) {
CitizenRef(UUID.fromString(data["owner"]))
} else {
creator
}
val workgroup = WorkgroupSimple(
val owner = data["owner"]?.let {
get<CitizenRepository>().findByUsername(it.toLowerCase().replace(' ', '-'))
} ?: creator
val workgroup = WorkgroupSimple<CitizenRef>(
id = UUID.fromString(data["id"] ?: UUID.randomUUID().toString()),
name = data["name"] ?: "Les Incoruptible",
description = data["description"] ?: "La vie est notre jeux",

View File

@@ -42,3 +42,70 @@ Feature: Workgroup
Then the response status code should be 200
And the response should contain object:
| $.result[0]id | 3fd8edb6-c4b4-4c94-bc75-ddd9b290d32c |
Scenario: Can add member to workgroup
Given I have citizen Blaise Pascal
And I have citizen Roger Penrose with id "6d883fe7-5fc0-4a50-8858-72230673eba4"
And I have citizen Alessandro Volta with id "b5bac515-45d4-4aeb-9b6d-2627a0bbc419"
And I am authenticated as Blaise Pascal
And I have workgroup:
| id | b0ea1922-3bc6-44e2-aa7c-40158998cfbb |
| name | Les bonobos |
| owner | Blaise Pascal |
When I send a POST request to "/workgroups/members/b0ea1922-3bc6-44e2-aa7c-40158998cfbb" with body:
"""
[
{"id":"6d883fe7-5fc0-4a50-8858-72230673eba4"},
{"id":"b5bac515-45d4-4aeb-9b6d-2627a0bbc419"}
]
"""
Then the response status code should be 200
Scenario: Can remove member to workgroup
Given I have citizen Heinrich Hertz
And I have citizen William Thomson with id "87909ba3-2069-431c-9924-219fd8411cf2"
And I have citizen Paul Dirac with id "1baf48bb-02bc-4d8f-ac86-33335354f5e7"
And I am authenticated as Heinrich Hertz
And I have workgroup:
| id | b6c975df-dd44-4e99-adc1-f605746b0e11 |
| name | Les Tacos |
| owner | Heinrich Hertz |
And I have members in workgroup "b6c975df-dd44-4e99-adc1-f605746b0e11":
| 87909ba3-2069-431c-9924-219fd8411cf2 |
| 1baf48bb-02bc-4d8f-ac86-33335354f5e7 |
When I send a DELETE request to "/workgroups/members/b6c975df-dd44-4e99-adc1-f605746b0e11" with body:
"""
[
{"id":"87909ba3-2069-431c-9924-219fd8411cf2"}
]
"""
Then the response status code should be 200
And the response should contain object:
| $.[0]id | 1baf48bb-02bc-4d8f-ac86-33335354f5e7 |
And the JSON should have 1 items
Scenario: Can update members on workgroup
Given I have citizen John Dalton
And I have citizen Sadi Carnot with id "be3b0926-8628-4426-804a-75188a6eb315"
And I have citizen Joseph Fourier with id "d9671eca-abaf-4b67-9230-3ece700c1ddb"
And I have citizen Georg Ohm with id "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1"
And I am authenticated as John Dalton
And I have workgroup:
| id | 784fe6bc-7635-4ae2-b080-3a4743b998bf |
| name | Les Tacos |
| owner | John Dalton |
And I have members in workgroup "784fe6bc-7635-4ae2-b080-3a4743b998bf":
| be3b0926-8628-4426-804a-75188a6eb315 |
| d9671eca-abaf-4b67-9230-3ece700c1ddb |
When I send a PUT request to "/workgroups/members/784fe6bc-7635-4ae2-b080-3a4743b998bf" with body:
"""
[
{"id":"be3b0926-8628-4426-804a-75188a6eb315"},
{"id":"b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1"}
]
"""
Then the response status code should be 200
And the response should contain object:
| $.[0]id | be3b0926-8628-4426-804a-75188a6eb315 |
| $.[1]id | b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1 |
And the JSON should have 2 items

View File

@@ -76,6 +76,13 @@ begin
assert not members::jsonb @> jsonb_build_array(jsonb_build_object('id', _citizen_id2)),
'Members must NOT contain citizen2';
select m into members from find_workgroup_members((created_workgroup->>'id')::uuid) m;
assert json_array_length(members) = 1, 'The members count must be equal to 1';
assert members::jsonb @> jsonb_build_array(jsonb_build_object('id', _citizen_id)),
'Members must contain citizen1';
assert not members::jsonb @> jsonb_build_array(jsonb_build_object('id', _citizen_id2)),
'Members must NOT contain citizen2';
rollback;
raise notice 'workgroup test pass';
end