Can get votes for one citizen
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
14
src/main/kotlin/fr/dcproject/routes/PaginatedRequest.kt
Normal file
14
src/main/kotlin/fr/dcproject/routes/PaginatedRequest.kt
Normal 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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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
130
src/test/kotlin/VoteTest.kt
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 |
|
||||||
|
|||||||
Reference in New Issue
Block a user