Move all file in fr.dcproject.
This commit is contained in:
14
src/main/kotlin/fr/dcproject/component/comment/KoinModule.kt
Normal file
14
src/main/kotlin/fr/dcproject/component/comment/KoinModule.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package fr.dcproject.component.comment
|
||||
|
||||
import fr.dcproject.component.comment.article.CommentArticleRepository
|
||||
import fr.dcproject.component.comment.constitution.CommentConstitutionRepository
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.CommentRepository
|
||||
import org.koin.dsl.module
|
||||
|
||||
val commentKoinModule = module {
|
||||
single { CommentRepository(get()) }
|
||||
single { CommentArticleRepository(get()) }
|
||||
single { CommentConstitutionRepository(get()) }
|
||||
single { CommentAccessControl() }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package fr.dcproject.component.comment.article
|
||||
|
||||
import fr.dcproject.common.entity.TargetI
|
||||
import fr.dcproject.component.article.ArticleForView
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.comment.generic.CommentForView
|
||||
import fr.dcproject.component.comment.generic.CommentRepositoryAbs
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.entity.UuidEntityI
|
||||
import java.util.UUID
|
||||
|
||||
class CommentArticleRepository(requester: Requester) : CommentRepositoryAbs<ArticleForView>(requester) {
|
||||
override fun findById(id: UUID): CommentForView<ArticleForView, CitizenRef>? {
|
||||
return requester
|
||||
.getFunction("find_comment_by_id")
|
||||
.selectOne(mapOf("id" to id))
|
||||
}
|
||||
|
||||
override fun findByCitizen(
|
||||
citizen: CitizenI,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<CommentForView<ArticleForView, CitizenRef>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_citizen")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"created_by_id" to citizen.id,
|
||||
"reference" to TargetI.getReference(ArticleRef::class)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findByTarget(
|
||||
target: UuidEntityI,
|
||||
page: Int,
|
||||
limit: Int,
|
||||
sort: Sort
|
||||
): Paginated<CommentForView<ArticleForView, CitizenRef>> = requester
|
||||
.getFunction("find_comments_by_target")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"target_id" to target.id,
|
||||
"sort" to sort.sql
|
||||
)
|
||||
|
||||
enum class Sort(val sql: String) {
|
||||
CREATED_AT("created_at"),
|
||||
VOTES("votes");
|
||||
|
||||
companion object {
|
||||
fun fromString(string: String): Sort? {
|
||||
return values().firstOrNull { it.sql == string }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package fr.dcproject.component.comment.article.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.article.CommentArticleRepository
|
||||
import fr.dcproject.component.comment.article.routes.CreateCommentArticle.PostArticleCommentRequest.Input
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.CommentForUpdate
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.post
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object CreateCommentArticle {
|
||||
@Location("/articles/{article}/comments")
|
||||
class PostArticleCommentRequest(article: UUID) {
|
||||
val article = ArticleRef(article)
|
||||
class Input(val content: String)
|
||||
}
|
||||
|
||||
suspend fun PostArticleCommentRequest.getComment(call: ApplicationCall) = call.receiveOrBadRequest<Input>().run {
|
||||
CommentForUpdate(
|
||||
target = article,
|
||||
createdBy = call.citizen,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
fun Route.createCommentArticle(repo: CommentArticleRepository, ac: CommentAccessControl) {
|
||||
post<PostArticleCommentRequest> {
|
||||
it.getComment(call).let { comment ->
|
||||
ac.assert { canCreate(comment, citizenOrNull) }
|
||||
repo.comment(comment)
|
||||
call.respond(HttpStatusCode.Created, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package fr.dcproject.component.comment.article.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.article.CommentArticleRepository
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
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.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetArticleComments {
|
||||
@Location("/articles/{article}/comments")
|
||||
class ArticleCommentsRequest(
|
||||
article: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
val search: String? = null,
|
||||
sort: String = CommentArticleRepository.Sort.CREATED_AT.sql
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val article = ArticleRef(article)
|
||||
val sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.fromString(sort) ?: CommentArticleRepository.Sort.CREATED_AT
|
||||
}
|
||||
|
||||
fun Route.getArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) {
|
||||
get<ArticleCommentsRequest> {
|
||||
val comment = repo.findByTarget(it.article, it.page, it.limit, it.sort)
|
||||
if (comment.result.isNotEmpty()) {
|
||||
ac.assert { canView(comment.result, citizenOrNull) }
|
||||
}
|
||||
call.respond(HttpStatusCode.OK, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.dcproject.component.comment.article.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.comment.article.CommentArticleRepository
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
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 GetCitizenArticleComments {
|
||||
@Location("/citizens/{citizen}/comments/articles")
|
||||
class CitizenCommentArticleRequest(citizen: UUID) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
}
|
||||
|
||||
fun Route.getCitizenArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) {
|
||||
get<CitizenCommentArticleRequest> {
|
||||
repo.findByCitizen(it.citizen).let { comments ->
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(comments)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package fr.dcproject.component.comment.article.routes
|
||||
|
||||
import fr.dcproject.component.comment.article.routes.CreateCommentArticle.createCommentArticle
|
||||
import fr.dcproject.component.comment.article.routes.GetArticleComments.getArticleComments
|
||||
import fr.dcproject.component.comment.article.routes.GetCitizenArticleComments.getCitizenArticleComments
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.routing.Routing
|
||||
import org.koin.ktor.ext.get
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Routing.installCommentArticleRoutes() {
|
||||
authenticate(optional = true) {
|
||||
getArticleComments(get(), get())
|
||||
createCommentArticle(get(), get())
|
||||
getCitizenArticleComments(get(), get())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package fr.dcproject.component.comment.constitution
|
||||
|
||||
import fr.dcproject.common.entity.TargetI
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.comment.article.CommentArticleRepository
|
||||
import fr.dcproject.component.comment.generic.CommentForView
|
||||
import fr.dcproject.component.comment.generic.CommentRepositoryAbs
|
||||
import fr.dcproject.component.constitution.ConstitutionRef
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.entity.UuidEntityI
|
||||
import java.util.UUID
|
||||
|
||||
class CommentConstitutionRepository(requester: Requester) : CommentRepositoryAbs<ConstitutionRef>(requester) {
|
||||
override fun findById(id: UUID): CommentForView<ConstitutionRef, CitizenRef>? {
|
||||
return requester
|
||||
.getFunction("find_comment_by_id")
|
||||
.selectOne(mapOf("id" to id))
|
||||
}
|
||||
|
||||
override fun findByCitizen(
|
||||
citizen: CitizenI,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<CommentForView<ConstitutionRef, CitizenRef>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_citizen")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"created_by_id" to citizen.id,
|
||||
"reference" to TargetI.getReference(ConstitutionRef::class)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findByTarget(
|
||||
target: UuidEntityI,
|
||||
page: Int,
|
||||
limit: Int,
|
||||
sort: CommentArticleRepository.Sort
|
||||
): Paginated<CommentForView<ConstitutionRef, CitizenRef>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_target")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"target_id" to target.id,
|
||||
"sort" to sort.sql
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package fr.dcproject.component.comment.constitution.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.constitution.CommentConstitutionRepository
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.CommentForUpdate
|
||||
import fr.dcproject.component.constitution.ConstitutionRef
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.post
|
||||
import io.ktor.request.receiveText
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object CreateConstitutionComment {
|
||||
@Location("/constitutions/{constitution}/comments")
|
||||
class CreateConstitutionCommentRequest(constitution: UUID) {
|
||||
val constitution = ConstitutionRef(constitution)
|
||||
}
|
||||
|
||||
fun Route.createConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
|
||||
post<CreateConstitutionCommentRequest> {
|
||||
val content = call.receiveText()
|
||||
val comment = CommentForUpdate(
|
||||
target = it.constitution,
|
||||
createdBy = citizen,
|
||||
content = content
|
||||
)
|
||||
ac.assert { canCreate(comment, citizenOrNull) }
|
||||
repo.comment(comment)
|
||||
|
||||
call.respond(HttpStatusCode.Created, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fr.dcproject.component.comment.constitution.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.comment.constitution.CommentConstitutionRepository
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
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 GetCitizenCommentConstitution {
|
||||
@Location("/citizens/{citizen}/comments/constitutions")
|
||||
class GetCitizenCommentConstitutionRequest(citizen: UUID) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
}
|
||||
|
||||
fun Route.getCitizenCommentConstitution(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
|
||||
get<GetCitizenCommentConstitutionRequest> {
|
||||
val comments = repo.findByCitizen(it.citizen)
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(comments)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.dcproject.component.comment.constitution.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.constitution.CommentConstitutionRepository
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.constitution.ConstitutionRef
|
||||
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.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetConstitutionComment {
|
||||
@Location("/constitutions/{constitution}/comments")
|
||||
class GetConstitutionCommentRequest(constitution: UUID) {
|
||||
val constitution = ConstitutionRef(constitution)
|
||||
}
|
||||
|
||||
fun Route.getConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
|
||||
get<GetConstitutionCommentRequest> {
|
||||
val comments = repo.findByTarget(it.constitution)
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(HttpStatusCode.OK, comments)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package fr.dcproject.component.comment.constitution.routes
|
||||
|
||||
import fr.dcproject.component.comment.constitution.routes.CreateConstitutionComment.createConstitutionComment
|
||||
import fr.dcproject.component.comment.constitution.routes.GetCitizenCommentConstitution.getCitizenCommentConstitution
|
||||
import fr.dcproject.component.comment.constitution.routes.GetConstitutionComment.getConstitutionComment
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.routing.Routing
|
||||
import org.koin.ktor.ext.get
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Routing.installCommentConstitutionRoutes() {
|
||||
authenticate(optional = true) {
|
||||
createConstitutionComment(get(), get())
|
||||
getCitizenCommentConstitution(get(), get())
|
||||
getConstitutionComment(get(), get())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package fr.dcproject.component.comment.generic
|
||||
|
||||
import fr.dcproject.common.entity.EntityI
|
||||
import fr.dcproject.common.entity.ExtraI
|
||||
import fr.dcproject.common.entity.HasTarget
|
||||
import fr.dcproject.common.entity.TargetI
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.vote.entity.Votable
|
||||
import fr.dcproject.component.vote.entity.VotableImp
|
||||
import fr.postgresjson.entity.EntityCreatedAt
|
||||
import fr.postgresjson.entity.EntityCreatedAtImp
|
||||
import fr.postgresjson.entity.EntityCreatedBy
|
||||
import fr.postgresjson.entity.EntityCreatedByImp
|
||||
import fr.postgresjson.entity.EntityDeletedAt
|
||||
import fr.postgresjson.entity.EntityDeletedAtImp
|
||||
import fr.postgresjson.entity.EntityUpdatedAt
|
||||
import fr.postgresjson.entity.EntityUpdatedAtImp
|
||||
import org.joda.time.DateTime
|
||||
import java.util.UUID
|
||||
|
||||
class CommentForView<T : TargetI, C : CitizenRef>(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
override val createdBy: C,
|
||||
override val target: T,
|
||||
override var content: String,
|
||||
override val parent: CommentParent<T>? = null,
|
||||
val childrenCount: Int? = null,
|
||||
override val deletedAt: DateTime? = null
|
||||
) : ExtraI<T, C>,
|
||||
CommentWithParentI<T>,
|
||||
CommentForUpdate<T, C>(id, createdBy, target, content, parent, deletedAt),
|
||||
CommentWithTargetI<T>,
|
||||
EntityCreatedBy<C> by EntityCreatedByImp(createdBy),
|
||||
EntityUpdatedAt by EntityUpdatedAtImp(),
|
||||
EntityDeletedAt by EntityDeletedAtImp(),
|
||||
Votable by VotableImp(),
|
||||
TargetI {
|
||||
constructor(
|
||||
createdBy: C,
|
||||
parent: CommentParent<T>,
|
||||
content: String
|
||||
) : this(
|
||||
createdBy = createdBy,
|
||||
parent = parent,
|
||||
target = parent.target,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
open class CommentForUpdate<T : TargetI, C : CitizenRef>(
|
||||
override val id: UUID = UUID.randomUUID(),
|
||||
override val createdBy: C,
|
||||
override val target: T,
|
||||
open var content: String,
|
||||
override val parent: CommentParent<T>? = null,
|
||||
override val deletedAt: DateTime? = null
|
||||
) : CommentParent<T>(id, deletedAt, target),
|
||||
CommentWithParentI<T>,
|
||||
ExtraI<T, C>,
|
||||
CommentWithTargetI<T>,
|
||||
EntityCreatedAt by EntityCreatedAtImp(),
|
||||
EntityCreatedBy<C>,
|
||||
EntityDeletedAt,
|
||||
TargetI {
|
||||
constructor(
|
||||
createdBy: C,
|
||||
parent: CommentParent<T>,
|
||||
content: String
|
||||
) : this(
|
||||
createdBy = createdBy,
|
||||
parent = parent,
|
||||
target = parent.target,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
open class CommentParent<T : TargetI>(
|
||||
override val id: UUID,
|
||||
override val deletedAt: DateTime?,
|
||||
override val target: T
|
||||
) : CommentRef(id),
|
||||
CommentParentI<T>
|
||||
|
||||
interface CommentParentI<T : TargetI> : CommentI, EntityDeletedAt, CommentWithTargetI<T>
|
||||
|
||||
interface CommentWithTargetI<T : TargetI> : CommentI, TargetI, HasTarget<T>
|
||||
|
||||
interface CommentWithParentI<T : TargetI> {
|
||||
val parent: CommentParent<T>?
|
||||
}
|
||||
|
||||
open class CommentRef(id: UUID = UUID.randomUUID()) : CommentI, TargetRef(id)
|
||||
|
||||
interface CommentI : EntityI
|
||||
@@ -0,0 +1,41 @@
|
||||
package fr.dcproject.component.comment.generic
|
||||
|
||||
import fr.dcproject.common.entity.HasTarget
|
||||
import fr.dcproject.common.security.AccessControl
|
||||
import fr.dcproject.common.security.AccessResponse
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.postgresjson.entity.EntityCreatedBy
|
||||
import fr.postgresjson.entity.EntityDeletedAt
|
||||
|
||||
class CommentAccessControl : AccessControl() {
|
||||
fun <S> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse
|
||||
where S : CommentI,
|
||||
S : EntityDeletedAt = canAll(subjects) { canView(it, citizen) }
|
||||
|
||||
fun <S> canView(subject: S, citizen: CitizenI?): AccessResponse
|
||||
where S : CommentI,
|
||||
S : EntityDeletedAt = when {
|
||||
subject.isDeleted() -> denied("Your cannot view a deleted comment", "comment.view.deleted")
|
||||
else -> granted()
|
||||
}
|
||||
|
||||
fun <S, CR : CitizenI> canCreate(subject: S, citizen: CitizenI?): AccessResponse
|
||||
where S : CommentI,
|
||||
S : EntityCreatedBy<CR>,
|
||||
S : CommentWithParentI<*>,
|
||||
S : HasTarget<*> = when {
|
||||
citizen == null -> denied("You must be connected to create user", "comment.create.notConnected")
|
||||
subject.createdBy.id != citizen.id -> denied("You cannot create a comment with other user than yours", "comment.create.wrongUser")
|
||||
subject.parent?.isDeleted() ?: false -> denied("You cannot create a comment on deleted parent", "comment.create.deletedParent")
|
||||
subject.target.let { it is EntityDeletedAt && it.isDeleted() } -> denied("You cannot create a comment on deleted target", "comment.create.deletedTarget")
|
||||
else -> granted()
|
||||
}
|
||||
|
||||
fun <S, CR : CitizenI> canUpdate(subject: S, citizen: CitizenI?): AccessResponse
|
||||
where S : CommentI,
|
||||
S : EntityCreatedBy<CR> = when {
|
||||
citizen == null -> denied("You must be connected to update comment", "comment.update.notConnected")
|
||||
citizen.id != subject.createdBy.id -> denied("You cannot update another user of yours", "comment.update.notYours")
|
||||
else -> granted()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package fr.dcproject.component.comment.generic
|
||||
|
||||
import fr.dcproject.common.entity.TargetI
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.comment.article.CommentArticleRepository
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.entity.UuidEntityI
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import java.util.UUID
|
||||
|
||||
abstract class CommentRepositoryAbs<T : TargetI>(override var requester: Requester) : RepositoryI {
|
||||
abstract fun findById(id: UUID): CommentForView<T, CitizenRef>?
|
||||
|
||||
abstract fun findByCitizen(
|
||||
citizen: CitizenI,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentForView<T, CitizenRef>>
|
||||
|
||||
open fun findByParent(
|
||||
parent: CommentForView<T, CitizenRef>,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentForView<T, CitizenRef>> {
|
||||
return findByParent(parent.id, page, limit)
|
||||
}
|
||||
|
||||
open fun findByParent(
|
||||
parentId: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentForView<T, CitizenRef>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_parent")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"parent_id" to parentId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open fun findByTarget(
|
||||
target: UuidEntityI,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.CREATED_AT
|
||||
): Paginated<CommentForView<T, CitizenRef>> {
|
||||
return findByTarget(target.id, page, limit, sort)
|
||||
}
|
||||
|
||||
open fun findByTarget(
|
||||
targetId: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.CREATED_AT
|
||||
): Paginated<CommentForView<T, CitizenRef>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_target")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"target_id" to targetId,
|
||||
"sort" to sort.sql
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun <I : TargetI, C : CitizenRef> comment(comment: CommentForUpdate<I, C>) {
|
||||
requester
|
||||
.getFunction("comment")
|
||||
.sendQuery(
|
||||
"reference" to comment.target.reference,
|
||||
"resource" to comment
|
||||
)
|
||||
}
|
||||
|
||||
fun <I : T> edit(comment: CommentForUpdate<I, CitizenRef>) {
|
||||
requester
|
||||
.getFunction("edit_comment")
|
||||
.sendQuery(
|
||||
"id" to comment.id,
|
||||
"content" to comment.content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class CommentRepository(requester: Requester) : CommentRepositoryAbs<TargetRef>(requester) {
|
||||
override fun findById(id: UUID): CommentForView<TargetRef, CitizenRef>? {
|
||||
return requester
|
||||
.getFunction("find_comment_by_id")
|
||||
.selectOne(mapOf("id" to id))
|
||||
}
|
||||
|
||||
override fun findByCitizen(
|
||||
citizen: CitizenI,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<CommentForView<TargetRef, CitizenRef>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_citizen")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"created_by_id" to citizen.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findByParent(
|
||||
parentId: UUID,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<CommentForView<TargetRef, CitizenRef>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_parent")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"parent_id" to parentId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.CommentForUpdate
|
||||
import fr.dcproject.component.comment.generic.CommentRef
|
||||
import fr.dcproject.component.comment.generic.CommentRepository
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.post
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object CreateCommentChildren {
|
||||
@Location("/comments/{comment}/children")
|
||||
class CreateCommentChildrenRequest(comment: UUID) {
|
||||
val comment = CommentRef(comment)
|
||||
class Input(val content: String)
|
||||
}
|
||||
|
||||
fun Route.createCommentChildren(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
post<CreateCommentChildrenRequest> {
|
||||
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
val newComment = CommentForUpdate(
|
||||
content = call.receiveOrBadRequest<CreateCommentChildrenRequest.Input>().content,
|
||||
createdBy = citizen,
|
||||
parent = parent
|
||||
)
|
||||
|
||||
ac.assert { canCreate(newComment, citizenOrNull) }
|
||||
repo.comment(newComment)
|
||||
|
||||
call.respond(HttpStatusCode.Created, newComment)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.CommentRef
|
||||
import fr.dcproject.component.comment.generic.CommentRepository
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.put
|
||||
import io.ktor.request.receiveText
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object EditComment {
|
||||
@Location("/comments/{comment}")
|
||||
class EditCommentRequest(comment: UUID) {
|
||||
val comment = CommentRef(comment)
|
||||
}
|
||||
|
||||
fun Route.editComment(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
put<EditCommentRequest> {
|
||||
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
ac.assert { canUpdate(comment, citizenOrNull) }
|
||||
|
||||
comment.content = call.receiveText()
|
||||
repo.edit(comment)
|
||||
|
||||
call.respond(HttpStatusCode.OK, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.CommentRepository
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
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.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetCommentChildren {
|
||||
@Location("/comments/{comment}/children")
|
||||
class CommentChildrenRequest(
|
||||
val comment: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
val search: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
|
||||
fun Route.getChildrenComments(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
get<CommentChildrenRequest> {
|
||||
val comments =
|
||||
repo.findByParent(
|
||||
it.comment,
|
||||
it.page,
|
||||
it.limit
|
||||
)
|
||||
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
|
||||
call.respond(HttpStatusCode.OK, comments)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.CommentRef
|
||||
import fr.dcproject.component.comment.generic.CommentRepository
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
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 GetOneComment {
|
||||
@Location("/comments/{comment}")
|
||||
class CommentRequest(comment: UUID) {
|
||||
val comment = CommentRef(comment)
|
||||
}
|
||||
|
||||
fun Route.getOneComment(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
get<CommentRequest> {
|
||||
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment ${it.comment.id} not found")
|
||||
ac.assert { canView(comment, citizenOrNull) }
|
||||
|
||||
call.respond(HttpStatusCode.OK, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.component.comment.generic.routes.CreateCommentChildren.createCommentChildren
|
||||
import fr.dcproject.component.comment.generic.routes.EditComment.editComment
|
||||
import fr.dcproject.component.comment.generic.routes.GetCommentChildren.getChildrenComments
|
||||
import fr.dcproject.component.comment.generic.routes.GetOneComment.getOneComment
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.routing.Routing
|
||||
import org.koin.ktor.ext.get
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Routing.installCommentRoutes() {
|
||||
authenticate(optional = true) {
|
||||
editComment(get(), get())
|
||||
getOneComment(get(), get())
|
||||
createCommentChildren(get(), get())
|
||||
getChildrenComments(get(), get())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user