diff --git a/src/main/kotlin/fr/dcproject/component/comment/constitution/routes/CreateConstitutionComment.kt b/src/main/kotlin/fr/dcproject/component/comment/constitution/routes/CreateConstitutionComment.kt
index cf368e4..0b700f0 100644
--- a/src/main/kotlin/fr/dcproject/component/comment/constitution/routes/CreateConstitutionComment.kt
+++ b/src/main/kotlin/fr/dcproject/component/comment/constitution/routes/CreateConstitutionComment.kt
@@ -1,5 +1,6 @@
package fr.dcproject.component.comment.constitution.routes
+import fr.dcproject.application.http.badRequestIfNotValid
import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest
@@ -12,6 +13,9 @@ import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.database.CommentForUpdate
import fr.dcproject.component.comment.toOutput
import fr.dcproject.component.constitution.database.ConstitutionRef
+import io.konform.validation.Validation
+import io.konform.validation.jsonschema.maxLength
+import io.konform.validation.jsonschema.minLength
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -26,27 +30,37 @@ object CreateConstitutionComment {
@Location("/constitutions/{constitution}/comments")
class CreateConstitutionCommentRequest(constitution: UUID) {
val constitution = ConstitutionRef(constitution)
- class Input(val content: String)
+ class Input(val content: String) {
+ fun validate() = Validation {
+ Input::content {
+ minLength(20)
+ maxLength(6000)
+ }
+ }.validate(this)
+ }
}
fun Route.createConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
post {
mustBeAuth()
- call.receiveOrBadRequest().run {
- CommentForUpdate(
- target = it.constitution,
- createdBy = citizen,
- content = content
- )
- }.let { comment ->
- ac.assert { canCreate(comment, citizenOrNull) }
- repo.comment(comment)
- call.respond(
- HttpStatusCode.Created,
- comment.toOutput()
- )
- }
+ call.receiveOrBadRequest()
+ .apply { validate().badRequestIfNotValid() }
+ .run {
+ CommentForUpdate(
+ target = it.constitution,
+ createdBy = citizen,
+ content = content
+ )
+ }.let { comment ->
+ ac.assert { canCreate(comment, citizenOrNull) }
+ repo.comment(comment)
+
+ call.respond(
+ HttpStatusCode.Created,
+ comment.toOutput()
+ )
+ }
}
}
}
diff --git a/src/main/kotlin/fr/dcproject/component/comment/constitution/routes/GetConstitutionComment.kt b/src/main/kotlin/fr/dcproject/component/comment/constitution/routes/GetConstitutionComment.kt
index 40ae7ef..47d417e 100644
--- a/src/main/kotlin/fr/dcproject/component/comment/constitution/routes/GetConstitutionComment.kt
+++ b/src/main/kotlin/fr/dcproject/component/comment/constitution/routes/GetConstitutionComment.kt
@@ -1,5 +1,6 @@
package fr.dcproject.component.comment.constitution.routes
+import fr.dcproject.application.http.badRequestIfNotValid
import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizenOrNull
@@ -7,6 +8,12 @@ import fr.dcproject.component.comment.constitution.database.CommentConstitutionR
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.toOutput
import fr.dcproject.component.constitution.database.ConstitutionRef
+import fr.dcproject.routes.PaginatedRequest
+import fr.dcproject.routes.PaginatedRequestI
+import io.konform.validation.Validation
+import io.konform.validation.jsonschema.enum
+import io.konform.validation.jsonschema.maximum
+import io.konform.validation.jsonschema.minimum
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -19,12 +26,36 @@ import java.util.UUID
@KtorExperimentalLocationsAPI
object GetConstitutionComment {
@Location("/constitutions/{constitution}/comments")
- class GetConstitutionCommentRequest(constitution: UUID) {
+ class GetConstitutionCommentRequest(
+ constitution: UUID,
+ page: Int = 1,
+ limit: Int = 50,
+ val search: String? = null,
+ val sort: String = "createdAt"
+ ) : PaginatedRequestI by PaginatedRequest(page, limit) {
val constitution = ConstitutionRef(constitution)
+
+ fun validate() = Validation {
+ GetConstitutionCommentRequest::page {
+ minimum(1)
+ }
+ GetConstitutionCommentRequest::limit {
+ minimum(1)
+ maximum(50)
+ }
+ GetConstitutionCommentRequest::sort ifPresent {
+ enum(
+ "votes",
+ "createdAt",
+ )
+ }
+ }.validate(this)
}
fun Route.getConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
get {
+ it.validate().badRequestIfNotValid()
+
val comments = repo.findByTarget(it.constitution)
ac.assert { canView(comments.result, citizenOrNull) }
call.respond(
diff --git a/src/main/resources/openapi.yaml b/src/main/resources/openapi.yaml
index ab15282..c0a4348 100644
--- a/src/main/resources/openapi.yaml
+++ b/src/main/resources/openapi.yaml
@@ -522,13 +522,13 @@ paths:
in: query
required: false
example:
- - created_at
+ - createdAt
- votes
schema:
type: string
- default: created_at
+ default: createdAt
enum:
- - created_at
+ - createdAt
- votes
responses:
200:
@@ -707,13 +707,42 @@ paths:
tags:
- comment
- constitution
+ parameters:
+ - $ref: '#/components/parameters/page'
+ - $ref: '#/components/parameters/limit'
+ - $ref: '#/components/parameters/search'
+ - name: sort
+ in: query
+ required: false
+ example:
+ - createdAt
+ - votes
+ schema:
+ type: string
+ default: createdAt
+ enum:
+ - createdAt
+ - votes
responses:
200:
description: Return Comment and children
content:
application/json:
schema:
- $ref: '#/components/schemas/CommentResponse'
+ allOf:
+ - $ref: '#/components/schemas/Paginated'
+ - type: object
+ properties:
+ result:
+ type: array
+ items:
+ $ref: '#/components/schemas/CommentResponse'
+ 400:
+ description: BadReqest
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/400'
post:
security:
- JWTAuth: []
@@ -739,6 +768,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/CommentResponse'
+ 400:
+ description: BadReqest
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/400'
401:
$ref: '#/components/responses/401'
diff --git a/src/test/kotlin/integration/Comment constitutions routes.kt b/src/test/kotlin/integration/Comment constitutions routes.kt
index a999082..523a0be 100644
--- a/src/test/kotlin/integration/Comment constitutions routes.kt
+++ b/src/test/kotlin/integration/Comment constitutions routes.kt
@@ -1,6 +1,9 @@
package integration
import fr.dcproject.component.citizen.database.CitizenI.Name
+import integration.steps.`when`.Validate
+import integration.steps.`when`.Validate.ALL
+import integration.steps.`when`.Validate.REQUEST_BODY
import integration.steps.`when`.`When I send a GET request`
import integration.steps.`when`.`When I send a POST request`
import integration.steps.`when`.`with body`
@@ -13,6 +16,7 @@ import integration.steps.then.`And the response should contain`
import integration.steps.then.`And the response should not be null`
import integration.steps.then.`Then the response should be`
import integration.steps.then.and
+import io.ktor.http.HttpStatusCode.Companion.BadRequest
import io.ktor.http.HttpStatusCode.Companion.Created
import io.ktor.http.HttpStatusCode.Companion.OK
import org.junit.jupiter.api.Tag
@@ -33,12 +37,69 @@ class `Comment constitutions routes` : BaseTest() {
`with body`(
"""
{
- "content": "Hello mister"
+ "content": "Hello mister MARABOUTCHA"
}
"""
)
} `Then the response should be` Created and {
`And the response should not be null`()
+ `And the response should contain`("$.target.id", "1707c287-a472-4a62-89f2-9e85030e915c")
+ `And the response should contain`("$.content", "Hello mister MARABOUTCHA")
+ }
+ }
+ }
+
+ @Test
+ @Tag("BadRequest")
+ fun `I cannot comment constitution with bad request`() {
+ withIntegrationApplication {
+ `Given I have citizen`("Nicolas", "Copernic")
+ `Given I have constitution`(id = "aa16c635-28da-46f0-9a89-934eef88c7ca")
+ `When I send a POST request`("/constitutions/aa16c635-28da-46f0-9a89-934eef88c7ca/comments", ALL - REQUEST_BODY) {
+ `authenticated as`("Nicolas", "Copernic")
+ `with body`(
+ """
+ {
+ "content": "To small content"
+ }
+ """
+ )
+ } `Then the response should be` BadRequest and {
+ `And the response should not be null`()
+ `And the response should contain`("$.invalidParams[0].name", ".content")
+ `And the response should contain`("$.invalidParams[0].reason", "must have at least 20 characters")
+ }
+ }
+ }
+
+ @Test
+ fun `I can get all comment on constitution`() {
+ withIntegrationApplication {
+ `Given I have citizen`("Enrico", "Fermi")
+ `Given I have constitution`(id = "6166c078-ca97-4366-b0aa-2a5cd558c78a")
+ `Given I have comment on constitution`(constitution = "6166c078-ca97-4366-b0aa-2a5cd558c78a", createdBy = Name("Enrico", "Fermi"))
+ `When I send a GET request`("/constitutions/6166c078-ca97-4366-b0aa-2a5cd558c78a/comments?page=1&limit=40&sort=votes") {
+ `authenticated as`("Enrico", "Fermi")
+ } `Then the response should be` OK and {
+ `And the response should not be null`()
+ `And the response should contain`("$.result[0].target.id", "6166c078-ca97-4366-b0aa-2a5cd558c78a")
+ }
+ }
+ }
+
+ @Test
+ @Tag("BadRequest")
+ fun `I cannot get all comment on constitution with wrong parameters`() {
+ withIntegrationApplication {
+ `Given I have citizen`("Enrico", "Fermi")
+ `Given I have constitution`(id = "6166c078-ca97-4366-b0aa-2a5cd558c78a")
+ `Given I have comment on constitution`(constitution = "6166c078-ca97-4366-b0aa-2a5cd558c78a", createdBy = Name("Enrico", "Fermi"))
+ `When I send a GET request`("/constitutions/6166c078-ca97-4366-b0aa-2a5cd558c78a/comments?page=1&limit=40&sort=wrong", ALL - Validate.REQUEST_PARAM) {
+ `authenticated as`("Enrico", "Fermi")
+ } `Then the response should be` BadRequest and {
+ `And the response should not be null`()
+ `And the response should contain`("$.invalidParams[*].name", ".sort")
+ `And the response should contain`("$.invalidParams[*].reason", "must be one of: 'votes', 'createdAt'")
}
}
}