Add validation on route Article versions
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
package fr.dcproject.application
|
||||
|
||||
import fr.dcproject.application.http.BadRequestException
|
||||
import fr.dcproject.application.http.HttpErrorBadRequest
|
||||
import fr.dcproject.application.http.HttpErrorBadRequest.InvalidParam
|
||||
import io.ktor.features.DataConversion
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.parameter.ParametersDefinition
|
||||
@@ -8,6 +12,7 @@ import org.koin.core.qualifier.Qualifier
|
||||
import java.util.UUID
|
||||
|
||||
private typealias ConverterDeclaration = DataConversion.Configuration.() -> Unit
|
||||
|
||||
private inline fun <reified T> DataConversion.Configuration.get(
|
||||
qualifier: Qualifier? = null,
|
||||
noinline parameters: ParametersDefinition? = null
|
||||
@@ -17,7 +22,21 @@ private inline fun <reified T> DataConversion.Configuration.get(
|
||||
val converters: ConverterDeclaration = {
|
||||
convert<UUID> {
|
||||
decode { values, _ ->
|
||||
values.singleOrNull()?.let { UUID.fromString(it) }
|
||||
try {
|
||||
values.singleOrNull()?.let { UUID.fromString(it) }
|
||||
} catch (e: Throwable) {
|
||||
throw BadRequestException(
|
||||
HttpErrorBadRequest(
|
||||
HttpStatusCode.BadRequest,
|
||||
invalidParams = listOf(
|
||||
InvalidParam(
|
||||
"ID",
|
||||
"ID must be UUID"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
encode { value ->
|
||||
|
||||
@@ -6,6 +6,7 @@ import fr.dcproject.component.auth.ForbiddenException
|
||||
import fr.dcproject.component.auth.user
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.features.ParameterConversionException
|
||||
import io.ktor.features.StatusPages
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.response.respond
|
||||
@@ -19,10 +20,6 @@ class HttpError(
|
||||
val detail: String? = null,
|
||||
) {
|
||||
val statusCode: Int = statusCode.value
|
||||
data class InvalidParam(
|
||||
val name: String,
|
||||
val reason: String
|
||||
)
|
||||
}
|
||||
|
||||
fun statusPagesInstallation(): StatusPages.Configuration.() -> Unit = {
|
||||
@@ -80,4 +77,12 @@ fun statusPagesInstallation(): StatusPages.Configuration.() -> Unit = {
|
||||
exception<BadRequestException> { e ->
|
||||
call.respond(HttpStatusCode.BadRequest, e.httpError)
|
||||
}
|
||||
exception<ParameterConversionException> { e ->
|
||||
val parent = e.cause
|
||||
if (parent is BadRequestException) {
|
||||
call.respond(HttpStatusCode.BadRequest, parent.httpError)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,69 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.common.validation.isUuid
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.database.ArticleForListing
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
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.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object FindArticleVersions {
|
||||
@Location("/articles/{article}/versions")
|
||||
class ArticleVersionsRequest(
|
||||
article: UUID,
|
||||
val article: String,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
val sort: String? = null,
|
||||
val direction: RepositoryI.Direction? = null,
|
||||
val search: 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 article = ArticleRef(article)
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
fun validate() = Validation<ArticleVersionsRequest> {
|
||||
ArticleVersionsRequest::page {
|
||||
minimum(1)
|
||||
maximum(100)
|
||||
}
|
||||
ArticleVersionsRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
ArticleVersionsRequest::sort ifPresent {
|
||||
enum(
|
||||
"title",
|
||||
"createdAt",
|
||||
"vote",
|
||||
"popularity",
|
||||
)
|
||||
}
|
||||
ArticleVersionsRequest::article {
|
||||
isUuid()
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) =
|
||||
findVersionsById(request.page, request.limit, request.article.id)
|
||||
findVersionsById(request.page, request.limit, request.article.toUUID())
|
||||
|
||||
fun Route.findArticleVersions(repo: ArticleRepository, ac: ArticleAccessControl) {
|
||||
get<ArticleVersionsRequest> {
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
repo.findVersions(it)
|
||||
.apply { ac.assert { canView(result, citizenOrNull) } }
|
||||
.run {
|
||||
|
||||
Reference in New Issue
Block a user