Can get votes for one citizen

This commit is contained in:
2019-10-01 13:58:25 +02:00
parent 5d1bed5f22
commit 5352c82bba
6 changed files with 221 additions and 7 deletions

View File

@@ -3,10 +3,13 @@ package fr.dcproject.repository
import fr.dcproject.entity.Article import fr.dcproject.entity.Article
import fr.dcproject.entity.Constitution import fr.dcproject.entity.Constitution
import fr.dcproject.entity.VoteAggregation import fr.dcproject.entity.VoteAggregation
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.UuidEntity import fr.postgresjson.entity.UuidEntity
import fr.postgresjson.repository.RepositoryI import fr.postgresjson.repository.RepositoryI
import java.util.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
import fr.dcproject.entity.Citizen as CitizenEntity
import fr.dcproject.entity.Vote as VoteEntity import fr.dcproject.entity.Vote as VoteEntity
open class Vote <T: UuidEntity>(override var requester: Requester): RepositoryI<VoteEntity<T>> { open class Vote <T: UuidEntity>(override var requester: Requester): RepositoryI<VoteEntity<T>> {
@@ -26,6 +29,26 @@ open class Vote <T: UuidEntity>(override var requester: Requester): RepositoryI<
"anonymous" to anonymous "anonymous" to anonymous
)!! )!!
} }
open fun findByCitizen(
citizen: CitizenEntity,
page: Int = 1,
limit: Int = 50
): Paginated<VoteEntity<T>> =
findByCitizen(citizen.id ?: error("The citizen must have an id"), page, limit)
open fun findByCitizen(
citizenId: UUID,
page: Int = 1,
limit: Int = 50
): Paginated<VoteEntity<T>> {
return requester.run {
getFunction("find_votes_by_citizen")
.select(page, limit,
"created_by_id" to citizenId
)
}
}
} }
class VoteArticle (requester: Requester): Vote<Article>(requester) class VoteArticle (requester: Requester): Vote<Article>(requester)

View File

@@ -0,0 +1,14 @@
package fr.dcproject.routes
interface PaginatedRequestI {
val page: Int
val limit: Int
}
open class PaginatedRequest(
page: Int = 1,
limit: Int = 50
): PaginatedRequestI {
override val page: Int = if (page < 1) 1 else page
override val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
}

View File

@@ -4,11 +4,13 @@ import fr.dcproject.citizen
import fr.dcproject.entity.Citizen import fr.dcproject.entity.Citizen
import fr.dcproject.routes.VoteArticlePaths.ArticleVoteRequest.Content import fr.dcproject.routes.VoteArticlePaths.ArticleVoteRequest.Content
import fr.dcproject.security.voter.VoteVoter.Action.CREATE import fr.dcproject.security.voter.VoteVoter.Action.CREATE
import fr.dcproject.security.voter.VoteVoter.Action.VIEW
import fr.dcproject.security.voter.assertCan import fr.dcproject.security.voter.assertCan
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
import io.ktor.locations.get
import io.ktor.locations.put import io.ktor.locations.put
import io.ktor.request.receive import io.ktor.request.receive
import io.ktor.response.respond import io.ktor.response.respond
@@ -19,10 +21,18 @@ import fr.dcproject.repository.VoteArticle as VoteArticleRepository
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
object VoteArticlePaths { object VoteArticlePaths {
@Location("/articles/{article}/vote") class ArticleVoteRequest(val article: ArticleEntity) { @Location("/articles/{article}/vote")
class ArticleVoteRequest(val article: ArticleEntity) {
data class Content(var note: Int) data class Content(var note: Int)
} }
@Location("/citizens/{citizen}/votes/articles") class CitizenVoteArticleRequest(val citizen: Citizen)
@Location("/citizens/{citizen}/votes/articles")
class CitizenVoteArticleRequest(
val citizen: Citizen,
page: Int = 1,
limit: Int = 50,
val search: String? = null
): PaginatedRequestI by PaginatedRequest(page, limit)
} }
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
@@ -38,4 +48,11 @@ fun Route.voteArticle(repo: VoteArticleRepository) {
val votes = repo.vote(vote) val votes = repo.vote(vote)
call.respond(HttpStatusCode.Created, votes) call.respond(HttpStatusCode.Created, votes)
} }
get<VoteArticlePaths.CitizenVoteArticleRequest> {
val votes = repo.findByCitizen(it.citizen)
assertCan(VIEW, votes.result)
call.respond(votes)
}
} }

View File

@@ -10,7 +10,10 @@ class VoteVoter: Voter {
} }
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean { override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
return action is Action && subject is VoteEntity<*>? return action is Action && (
subject is VoteEntity<*>?
|| subject is List<*>
)
} }
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote { override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
@@ -19,8 +22,25 @@ class VoteVoter: Voter {
return Vote.GRANTED return Vote.GRANTED
} }
if (action == Action.VIEW) { if (action == Action.VIEW && user != null) {
return Vote.GRANTED if (subject is VoteEntity<*>) {
return if (subject.createdBy?.userId != user.id) {
Vote.DENIED
} else {
Vote.GRANTED
}
}
if (subject is List<*>) {
subject.forEach {
if (it !is VoteEntity<*> || it.createdBy?.userId != user.id) {
return Vote.DENIED
}
}
return Vote.GRANTED
}
return Vote.DENIED
} }
return Vote.ABSTAIN return Vote.ABSTAIN

130
src/test/kotlin/VoteTest.kt Normal file
View File

@@ -0,0 +1,130 @@
import fr.dcproject.entity.Article
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.User
import fr.dcproject.entity.Vote
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.util.KtorExperimentalAPI
import org.amshove.kluent.`should equal`
import org.amshove.kluent.shouldBe
import org.intellij.lang.annotations.Language
import org.joda.time.DateTime
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(PER_CLASS)
class VoteTest {
@Language("JSON")
private val voteJson: String = """
{
"id": "032acc3d-e8c5-4cb2-9297-bec913ff8d9b",
"created_by": {
"id": "40c65a43-f9f8-45cd-aa31-953376e7c94a",
"name": {
"first_name": "Jaque",
"last_name": "Bono",
"civility": null
},
"birthday": "2019-10-01T10:59:40.570Z",
"user_id": null,
"vote_anonymous": true,
"follow_anonymous": true,
"user": {
"id": "f68df389-fb0d-423e-90fd-a140a9ed29b9",
"username": "jaque",
"blocked_at": null,
"plain_password": "azerty",
"roles": [],
"created_at": null,
"updated_at": null
},
"deleted": false,
"created_at": null,
"deleted_at": null
},
"target": {
"id": "90f28912-7bd5-4f37-a0ea-8620e3817d51",
"title": "Hello world!",
"anonymous": true,
"content": "bla bla bla",
"description": "this is the changement !",
"tags": [],
"draft": false,
"last_version": false,
"created_by": {
"id": "40c65a43-f9f8-45cd-aa31-953376e7c94a",
"name": {
"first_name": "Jaque",
"last_name": "Bono",
"civility": null
},
"birthday": "2019-10-01T10:59:40.570Z",
"user_id": null,
"vote_anonymous": true,
"follow_anonymous": true,
"user": {
"id": "f68df389-fb0d-423e-90fd-a140a9ed29b9",
"username": "jaque",
"blocked_at": null,
"plain_password": "azerty",
"roles": [],
"created_at": null,
"updated_at": null
},
"deleted": false,
"created_at": null,
"deleted_at": null
},
"votes": {
"up": 0,
"neutral": 0,
"down": 0,
"updated_at": null
},
"version_id": "48dad61e-c54b-4f4c-9f66-428f90b94045",
"version_number": null,
"deleted": false,
"created_at": null,
"deleted_at": null
},
"note": -1,
"anonymous": true,
"updated_at": null,
"created_at": null
}""".trimIndent()
@Test
fun `test Vote Article serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = Citizen(
name = Citizen.Name("Jaque", "Bono"),
birthday = DateTime.now(),
user = user
)
val article = Article(
title = "Hello world!",
content = "bla bla bla",
description = "this is the changement !",
createdBy = citizen
)
val vote = Vote(
createdBy = citizen,
target = article,
note = -1
)
vote.serialize().contains("""Hello world!""") shouldBe true
vote.serialize().contains("-1") shouldBe true
println(vote.serialize())
}
@Test
fun `test Vote Article Deserialize`() {
val vote: Vote<Article> = voteJson.deserialize()!!
vote.id.toString() `should equal` "032acc3d-e8c5-4cb2-9297-bec913ff8d9b"
vote.note.toString() `should equal` "-1"
}
}

View File

@@ -1,6 +1,6 @@
Feature: vote Article Feature: vote Article
Scenario: Vote article Scenario: Can Vote article
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
When I send a PUT request to "/articles/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/vote" with body: When I send a PUT request to "/articles/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/vote" with body:
""" """
@@ -10,7 +10,7 @@ Feature: vote Article
""" """
Then the response status code should be 201 Then the response status code should be 201
Scenario: Vote constitution Scenario: Can Vote constitution
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
When I send a PUT request to "/constitutions/64b1f265-bfb3-332b-eef9-d00f63a3beaa/vote" with body: When I send a PUT request to "/constitutions/64b1f265-bfb3-332b-eef9-d00f63a3beaa/vote" with body:
""" """
@@ -19,3 +19,13 @@ Feature: vote Article
} }
""" """
Then the response status code should be 201 Then the response status code should be 201
Scenario: Can get votes of current citizen
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
When I send a GET request to "/citizens/64b7b379-2298-43ec-b428-ba134930cabd/votes/articles"
Then the response status code should be 200
And the response should contain object:
| current_page | 1 |
| limit | 50 |
| total | 2 |
| result[0].note | -1 |