feature #7: Add routes for comment article
This commit is contained in:
@@ -18,6 +18,7 @@ import fr.dcproject.security.voter.CitizenVoter
|
||||
import fr.dcproject.security.voter.CommentVoter
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
import io.ktor.auth.Authentication
|
||||
@@ -39,6 +40,7 @@ import java.util.*
|
||||
import java.util.concurrent.CompletionException
|
||||
import fr.dcproject.repository.Article as RepositoryArticle
|
||||
import fr.dcproject.repository.Citizen as RepositoryCitizen
|
||||
import fr.dcproject.repository.CommentGeneric as CommentGenericRepository
|
||||
import fr.dcproject.repository.Constitution as RepositoryConstitution
|
||||
import fr.dcproject.repository.User as UserRepository
|
||||
|
||||
@@ -90,6 +92,14 @@ fun Application.module() {
|
||||
}
|
||||
}
|
||||
|
||||
convert<CommentEntityGeneric> {
|
||||
decode { values, _ ->
|
||||
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||
?: throw InternalError("Cannot convert $values to UUID")
|
||||
get<CommentGenericRepository>().findById(id) ?: throw InternalError("Comment $values not found")
|
||||
}
|
||||
}
|
||||
|
||||
convert<Citizen> {
|
||||
decode { values, _ ->
|
||||
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||
@@ -154,6 +164,8 @@ fun Application.module() {
|
||||
constitution(get())
|
||||
followArticle(get())
|
||||
followConstitution(get())
|
||||
comment(get())
|
||||
commentArticle(get())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import io.ktor.util.KtorExperimentalAPI
|
||||
import org.koin.dsl.module
|
||||
import fr.dcproject.repository.Article as ArticleRepository
|
||||
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||
import fr.dcproject.repository.CommentArticle as CommentArticleRepository
|
||||
import fr.dcproject.repository.CommentGeneric as CommentGenericRepository
|
||||
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
||||
import fr.dcproject.repository.FollowArticle as FollowArticleRepository
|
||||
import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository
|
||||
@@ -33,6 +35,10 @@ val Module = module {
|
||||
single { ConstitutionRepository(get()) }
|
||||
single { FollowArticleRepository(get()) }
|
||||
single { FollowConstitutionRepository(get()) }
|
||||
single { CommentGenericRepository(get()) }
|
||||
single { CommentArticleRepository(get()) }
|
||||
// TODO implment constitution
|
||||
// single { CommentConstitutionRepository(get()) }
|
||||
|
||||
single { Migrations(connection = get(), directory = config.sqlFiles) }
|
||||
}
|
||||
|
||||
17
src/main/kotlin/fr/dcproject/entity/Comment.kt
Normal file
17
src/main/kotlin/fr/dcproject/entity/Comment.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package fr.dcproject.entity
|
||||
|
||||
import fr.postgresjson.entity.EntityUpdatedAt
|
||||
import fr.postgresjson.entity.EntityUpdatedAtImp
|
||||
import fr.postgresjson.entity.UuidEntity
|
||||
import java.util.*
|
||||
|
||||
open class Comment <T: UuidEntity> (
|
||||
id: UUID = UUID.randomUUID(),
|
||||
createdBy: Citizen,
|
||||
override var target: T,
|
||||
var content: String,
|
||||
var responses: List<Comment<T>>? = null,
|
||||
var parent: Comment<T>? = null,
|
||||
var parentsIds: List<UUID>? = null
|
||||
): Extra<T>(id, createdBy),
|
||||
EntityUpdatedAt by EntityUpdatedAtImp()
|
||||
@@ -10,7 +10,7 @@ interface ExtraI <T: EntityI<UUID>>:
|
||||
var target: T
|
||||
}
|
||||
|
||||
abstract class Extra<T: EntityI<UUID>>(
|
||||
abstract class Extra<T: UuidEntity>(
|
||||
id: UUID? = UUID.randomUUID(),
|
||||
createdBy: Citizen
|
||||
):
|
||||
|
||||
139
src/main/kotlin/fr/dcproject/repository/Comment.kt
Normal file
139
src/main/kotlin/fr/dcproject/repository/Comment.kt
Normal file
@@ -0,0 +1,139 @@
|
||||
package fr.dcproject.repository
|
||||
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.entity.UuidEntity
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import java.util.*
|
||||
import kotlin.reflect.KClass
|
||||
import fr.dcproject.entity.Article as ArticleEntity
|
||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||
import fr.dcproject.entity.Comment as CommentEntity
|
||||
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
||||
|
||||
open class Comment <T: UuidEntity>(override var requester: Requester): RepositoryI<CommentEntity<T>> {
|
||||
override val entityName = CommentEntity::class as KClass<CommentEntity<T>>
|
||||
|
||||
open fun findByCitizen(
|
||||
citizen: CitizenEntity,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentEntity<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<CommentEntity<T>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_citizen")
|
||||
.select(page, limit,
|
||||
"created_by_id" to citizenId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open fun findByParent(
|
||||
parent: CommentEntity<T>,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentEntity<T>> {
|
||||
return findByParent(parent.id ?: error("comment must have an ID"), page, limit)
|
||||
}
|
||||
|
||||
open fun findByParent(
|
||||
parentId: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentEntity<T>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_parent")
|
||||
.select(page, limit,
|
||||
"parent_id" to parentId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open fun findByTarget(
|
||||
target: UuidEntity,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentEntity<T>> {
|
||||
return findByTarget(target.id ?: error("comment must have an ID"), page, limit)
|
||||
}
|
||||
|
||||
open fun findByTarget(
|
||||
targetId: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentEntity<T>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_target")
|
||||
.select(page, limit,
|
||||
"target_id" to targetId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun comment(comment: CommentEntity<T>) {
|
||||
val reference = comment.target::class.simpleName!!.toLowerCase()
|
||||
requester
|
||||
.getFunction("comment")
|
||||
.sendQuery(
|
||||
"reference" to reference,
|
||||
"target_id" to comment.target.id,
|
||||
"created_by_id" to comment.createdBy?.id,
|
||||
"content" to comment.content
|
||||
)
|
||||
}
|
||||
|
||||
fun edit(comment: CommentEntity<T>) {
|
||||
val reference = comment.target::class.simpleName!!.toLowerCase()
|
||||
requester
|
||||
.getFunction("edit_comment")
|
||||
.sendQuery(
|
||||
"reference" to reference,
|
||||
"id" to comment.target.id,
|
||||
"content" to comment.content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class CommentGeneric (requester: Requester): Comment<UuidEntity>(requester) {
|
||||
fun findById(id: UUID): CommentEntity<ArticleEntity>? {
|
||||
return requester
|
||||
.getFunction("find_comment_by_id")
|
||||
.selectOne(mapOf("id" to id))
|
||||
}
|
||||
}
|
||||
|
||||
class CommentArticle (requester: Requester): Comment<ArticleEntity>(requester) {
|
||||
override fun findByCitizen(
|
||||
citizenId: UUID,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<CommentEntity<ArticleEntity>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_article_by_citizen")
|
||||
.select(page, limit,
|
||||
"created_by_id" to citizenId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CommentConstitution (requester: Requester): Comment<ConstitutionEntity>(requester) {
|
||||
override fun findByCitizen(
|
||||
citizenId: UUID,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<CommentEntity<ConstitutionEntity>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_constitution_by_citizen")
|
||||
.select(page, limit,
|
||||
"created_by_id" to citizenId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/main/kotlin/fr/dcproject/routes/Comment.kt
Normal file
45
src/main/kotlin/fr/dcproject/routes/Comment.kt
Normal file
@@ -0,0 +1,45 @@
|
||||
package fr.dcproject.routes
|
||||
|
||||
import fr.dcproject.security.voter.CommentVoter.Action.UPDATE
|
||||
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
|
||||
import fr.dcproject.security.voter.assertCan
|
||||
import fr.postgresjson.entity.UuidEntity
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.locations.put
|
||||
import io.ktor.request.receiveText
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.*
|
||||
import fr.dcproject.entity.Comment as CommentEntity
|
||||
import fr.dcproject.repository.CommentGeneric as CommentRepository
|
||||
|
||||
typealias CommentEntityGeneric = CommentEntity<UuidEntity>
|
||||
@KtorExperimentalLocationsAPI
|
||||
object CommentPaths {
|
||||
// TODO: change UUID by entity converter
|
||||
@Location("/comments/{comment}") class CommentRequest(val comment: UUID)
|
||||
}
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.comment(repo: CommentRepository) {
|
||||
get<CommentPaths.CommentRequest> {
|
||||
val comment = repo.findById(it.comment)!!
|
||||
assertCan(VIEW, comment)
|
||||
|
||||
call.respond(HttpStatusCode.OK, comment)
|
||||
}
|
||||
|
||||
put<CommentPaths.CommentRequest> {
|
||||
val comment = repo.findById(it.comment)!!
|
||||
assertCan(UPDATE,comment)
|
||||
|
||||
comment.content = call.receiveText()
|
||||
repo.edit(comment as fr.dcproject.entity.Comment<UuidEntity>)
|
||||
|
||||
call.respond(HttpStatusCode.OK, comment)
|
||||
}
|
||||
}
|
||||
55
src/main/kotlin/fr/dcproject/routes/CommentArticle.kt
Normal file
55
src/main/kotlin/fr/dcproject/routes/CommentArticle.kt
Normal file
@@ -0,0 +1,55 @@
|
||||
package fr.dcproject.routes
|
||||
|
||||
import fr.dcproject.citizen
|
||||
import fr.dcproject.entity.Citizen
|
||||
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
|
||||
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
|
||||
import fr.dcproject.security.voter.assertCan
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.locations.post
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import fr.dcproject.entity.Article as ArticleEntity
|
||||
import fr.dcproject.entity.Comment as CommentEntity
|
||||
import fr.dcproject.repository.CommentArticle as CommentArticleRepository
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object CommentArticlePaths {
|
||||
@Location("/articles/{article}/comments") class ArticleCommentRequest(val article: ArticleEntity)
|
||||
@Location("/citizens/{citizen}/comments/articles") class CitizenCommentArticleRequest(val citizen: Citizen)
|
||||
}
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.commentArticle(repo: CommentArticleRepository) {
|
||||
get<CommentArticlePaths.ArticleCommentRequest> {
|
||||
assertCan(VIEW, it.article)
|
||||
|
||||
val comment = repo.findByTarget(it.article)
|
||||
|
||||
call.respond(HttpStatusCode.OK, comment)
|
||||
}
|
||||
|
||||
post<CommentArticlePaths.ArticleCommentRequest> {
|
||||
assertCan(CREATE, it.article)
|
||||
|
||||
val content = call.receive<String>()
|
||||
val comment = CommentEntity(
|
||||
target = it.article,
|
||||
createdBy = citizen,
|
||||
content = content
|
||||
)
|
||||
repo.comment(comment)
|
||||
|
||||
call.respond(HttpStatusCode.Created, comment)
|
||||
}
|
||||
|
||||
get<CommentArticlePaths.CitizenCommentArticleRequest> {
|
||||
val comments = repo.findByCitizen(it.citizen)
|
||||
call.respond(comments)
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class ArticleVoter: Voter {
|
||||
}
|
||||
|
||||
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
||||
return action is Action && subject is Article?
|
||||
return (action is Action || action is CommentVoter.Action) && subject is Article?
|
||||
}
|
||||
|
||||
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
||||
@@ -26,6 +26,14 @@ class ArticleVoter: Voter {
|
||||
return Vote.GRANTED
|
||||
}
|
||||
|
||||
if (action == CommentVoter.Action.CREATE) {
|
||||
return Vote.GRANTED
|
||||
}
|
||||
|
||||
if (action == CommentVoter.Action.VIEW) {
|
||||
return Vote.GRANTED
|
||||
}
|
||||
|
||||
if (action == Action.DELETE && user is User && subject is Article && subject.createdBy?.userId == user.id) {
|
||||
return Vote.GRANTED
|
||||
}
|
||||
|
||||
38
src/main/kotlin/fr/dcproject/security/voter/CommentVoter.kt
Normal file
38
src/main/kotlin/fr/dcproject/security/voter/CommentVoter.kt
Normal file
@@ -0,0 +1,38 @@
|
||||
package fr.dcproject.security.voter
|
||||
|
||||
import fr.dcproject.entity.Comment
|
||||
import io.ktor.application.ApplicationCall
|
||||
|
||||
class CommentVoter: Voter {
|
||||
enum class Action: ActionI {
|
||||
CREATE,
|
||||
UPDATE,
|
||||
VIEW,
|
||||
DELETE
|
||||
}
|
||||
|
||||
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
||||
return action is Action && subject is Comment<*>?
|
||||
}
|
||||
|
||||
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
||||
val user = call.user
|
||||
if (action == Action.CREATE && user != null) {
|
||||
return Vote.GRANTED
|
||||
}
|
||||
|
||||
if (action == Action.VIEW) {
|
||||
return Vote.GRANTED
|
||||
}
|
||||
|
||||
if (action == Action.UPDATE && user != null && subject is Comment<*> && user.id == subject.createdBy?.userId) {
|
||||
return Vote.GRANTED
|
||||
}
|
||||
|
||||
if (action == Action.DELETE) {
|
||||
return Vote.DENIED
|
||||
}
|
||||
|
||||
return Vote.ABSTAIN
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user