Continue to implement opinion
improve target reference Improve Tests for Opinion fix SQL:upsert_opinion
This commit is contained in:
@@ -197,6 +197,7 @@ fun Application.module(env: Env = PROD) {
|
||||
commentConstitution(get())
|
||||
voteArticle(get(), get(), get())
|
||||
voteConstitution(get())
|
||||
opinionArticle(get())
|
||||
opinionChoice(get())
|
||||
definition()
|
||||
}
|
||||
@@ -215,6 +216,9 @@ fun Application.module(env: Env = PROD) {
|
||||
exception<NotFoundException> { e ->
|
||||
call.respond(HttpStatusCode.BadRequest, e.message!!)
|
||||
}
|
||||
exception<ForbiddenException> {
|
||||
call.respond(HttpStatusCode.Forbidden)
|
||||
}
|
||||
}
|
||||
|
||||
install(CORS) {
|
||||
|
||||
@@ -32,8 +32,6 @@ open class Comment<T : TargetI>(
|
||||
target = parent.target,
|
||||
content = content
|
||||
)
|
||||
|
||||
override val reference get() = TargetI.getReference(this)
|
||||
}
|
||||
|
||||
open class CommentRef(id: UUID = UUID.randomUUID()) : CommentS(id)
|
||||
|
||||
@@ -6,7 +6,7 @@ import fr.postgresjson.entity.immutable.UuidEntity
|
||||
import fr.postgresjson.entity.immutable.UuidEntityI
|
||||
import java.util.*
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSuperclassOf
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
interface ExtraI<T : TargetI> :
|
||||
UuidEntityI,
|
||||
@@ -26,16 +26,18 @@ interface TargetI : UuidEntityI {
|
||||
enum class TargetName(val targetReference: String) {
|
||||
Article("article"),
|
||||
Constitution("constitution"),
|
||||
Comment("comment")
|
||||
Comment("comment"),
|
||||
Opinion("opinion")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T : TargetI> getReference(t: KClass<T>): String {
|
||||
return when {
|
||||
t.isSuperclassOf(Article::class) -> TargetName.Article.targetReference
|
||||
t.isSuperclassOf(Constitution::class) -> TargetName.Constitution.targetReference
|
||||
t.isSuperclassOf(Comment::class) -> TargetName.Comment.targetReference
|
||||
else -> throw error("target not implemented")
|
||||
t.isSubclassOf(ArticleRef::class) -> TargetName.Article.targetReference
|
||||
t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference
|
||||
t.isSubclassOf(CommentRef::class) -> TargetName.Comment.targetReference
|
||||
t.isSubclassOf(Opinion::class) -> TargetName.Opinion.targetReference
|
||||
else -> throw error("target not implemented: ${t.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package fr.dcproject.entity
|
||||
|
||||
import fr.postgresjson.entity.immutable.*
|
||||
import fr.postgresjson.entity.immutable.EntityCreatedAt
|
||||
import fr.postgresjson.entity.immutable.EntityCreatedAtImp
|
||||
import fr.postgresjson.entity.immutable.EntityCreatedBy
|
||||
import fr.postgresjson.entity.immutable.EntityCreatedByImp
|
||||
import java.util.*
|
||||
|
||||
open class Opinion<T : TargetI>(
|
||||
@@ -9,11 +12,16 @@ open class Opinion<T : TargetI>(
|
||||
override val target: T,
|
||||
val choice: OpinionChoice
|
||||
) : ExtraI<T>,
|
||||
UuidEntity(id),
|
||||
TargetRef(id),
|
||||
EntityCreatedAt by EntityCreatedAtImp(),
|
||||
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy) {
|
||||
|
||||
fun getName(): String = choice.name
|
||||
}
|
||||
|
||||
typealias OpinionArticle = Opinion<Article>
|
||||
class OpinionArticle(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
createdBy: CitizenBasic,
|
||||
target: ArticleRef,
|
||||
choice: OpinionChoice
|
||||
) : Opinion<ArticleRef>(id, createdBy, target, choice)
|
||||
@@ -10,7 +10,7 @@ import java.util.*
|
||||
class OpinionChoice(
|
||||
id: UUID,
|
||||
val name: String,
|
||||
val target: List<String>
|
||||
val target: List<String>?
|
||||
) : OpinionChoiceRef(id),
|
||||
EntityCreatedAt by EntityCreatedAtImp(),
|
||||
EntityDeletedAt by EntityDeletedAtImp()
|
||||
|
||||
@@ -3,13 +3,13 @@ package fr.dcproject.entity
|
||||
import fr.postgresjson.entity.EntityI
|
||||
|
||||
class OpinionAggregation(
|
||||
override val entries: Set<Map.Entry<String, Int>> = emptySet()
|
||||
) : AbstractMap<String, Int>(), EntityI
|
||||
private val underlying: MutableMap<String, Any> = mutableMapOf()
|
||||
) : MutableMap<String, Any> by underlying, EntityI
|
||||
|
||||
interface Opinionable {
|
||||
val opinions: MutableMap<String, Int>
|
||||
var opinions: MutableMap<String, Int>
|
||||
}
|
||||
|
||||
class OpinionableImp : Opinionable {
|
||||
override val opinions: MutableMap<String, Int> = mutableMapOf()
|
||||
override var opinions: MutableMap<String, Int> = mutableMapOf()
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package fr.dcproject.entity.request
|
||||
|
||||
import fr.dcproject.entity.Citizen
|
||||
import fr.dcproject.entity.OpinionArticle
|
||||
import fr.dcproject.entity.OpinionChoiceRef
|
||||
import fr.dcproject.entity.TargetRef
|
||||
import fr.dcproject.repository.Article
|
||||
import fr.dcproject.repository.OpinionChoice
|
||||
import fr.dcproject.utils.toUUID
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
|
||||
class ArticleOpinionRequest(
|
||||
target: String,
|
||||
opinionChoice: String
|
||||
) : RequestBuilderWithCreator<Citizen, OpinionArticle>, KoinComponent {
|
||||
val target = TargetRef(target.toUUID())
|
||||
val opinionChoice = OpinionChoiceRef(opinionChoice.toUUID())
|
||||
|
||||
override fun create(citizen: Citizen): OpinionArticle {
|
||||
return OpinionArticle(
|
||||
choice = get<OpinionChoice>().findOpinionChoiceById(opinionChoice.id)!!,
|
||||
target = get<Article>().findById(target.id)!!,
|
||||
createdBy = citizen
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
package fr.dcproject.entity.request
|
||||
|
||||
import fr.dcproject.entity.CitizenRef
|
||||
import fr.postgresjson.entity.EntityI
|
||||
import io.ktor.application.ApplicationCall
|
||||
|
||||
interface Request
|
||||
|
||||
interface RequestBuilder<E: EntityI> : Request {
|
||||
fun create(): E
|
||||
interface RequestBuilder<E> {
|
||||
suspend fun getContent(call: ApplicationCall): E
|
||||
}
|
||||
|
||||
interface RequestBuilderWithCreator<C: CitizenRef, E: EntityI> : Request {
|
||||
fun create(citizen: C): E
|
||||
}
|
||||
suspend fun <E> ApplicationCall.getContent(builder: RequestBuilder<E>) = builder.getContent(this)
|
||||
|
||||
@@ -11,6 +11,7 @@ import net.pearx.kasechange.toSnakeCase
|
||||
import java.util.*
|
||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||
import fr.dcproject.entity.Opinion as OpinionEntity
|
||||
import fr.dcproject.entity.OpinionArticle as OpinionArticleEntity
|
||||
import fr.dcproject.entity.OpinionChoice as OpinionChoiceEntity
|
||||
|
||||
open class OpinionChoice(override val requester: Requester) : RepositoryI {
|
||||
@@ -25,6 +26,14 @@ open class OpinionChoice(override val requester: Requester) : RepositoryI {
|
||||
"targets" to targets
|
||||
)
|
||||
|
||||
/**
|
||||
* find opinion choices by name
|
||||
*/
|
||||
fun findOpinionsChoiceByName(name: String): OpinionChoiceEntity? =
|
||||
findOpinionsChoices().first {
|
||||
it.name == name
|
||||
}
|
||||
|
||||
/**
|
||||
* find one opinion choices by id
|
||||
*/
|
||||
@@ -104,9 +113,18 @@ open class Opinion<T : TargetRef>(requester: Requester) : OpinionChoice(requeste
|
||||
.select(page, limit,
|
||||
"sort" to sort?.toSnakeCase(),
|
||||
"direction" to direction,
|
||||
"citizen" to citizen.id
|
||||
"citizen_id" to citizen.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class OpinionArticle(requester: Requester) : Opinion<Article>(requester)
|
||||
class OpinionArticle(requester: Requester) : Opinion<Article>(requester) {
|
||||
/**
|
||||
* Create an Opinion on Article
|
||||
*/
|
||||
fun opinion(opinion: OpinionArticleEntity): OpinionArticleEntity {
|
||||
return requester
|
||||
.getFunction("upsert_opinion")
|
||||
.selectOne(opinion) ?: error("query 'upsert_opinion' return null")
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
package fr.dcproject.routes
|
||||
|
||||
import fr.dcproject.citizen
|
||||
import fr.dcproject.entity.request.ArticleOpinionRequest
|
||||
import fr.dcproject.entity.Citizen
|
||||
import fr.dcproject.entity.OpinionArticle
|
||||
import fr.dcproject.entity.OpinionChoiceRef
|
||||
import fr.dcproject.entity.request.RequestBuilder
|
||||
import fr.dcproject.entity.request.getContent
|
||||
import fr.dcproject.repository.OpinionChoice
|
||||
import fr.dcproject.security.voter.OpinionVoter.Action.VIEW
|
||||
import fr.dcproject.security.voter.assertCan
|
||||
import fr.dcproject.utils.toUUID
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.BadRequestException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -17,6 +24,7 @@ import io.ktor.routing.Route
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
import java.util.*
|
||||
import fr.dcproject.entity.Article as ArticleEntity
|
||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||
import fr.dcproject.repository.OpinionArticle as OpinionArticleRepository
|
||||
@@ -24,7 +32,7 @@ import fr.dcproject.repository.OpinionArticle as OpinionArticleRepository
|
||||
@KtorExperimentalLocationsAPI
|
||||
object OpinionArticlePaths {
|
||||
/**
|
||||
* Get paginated opinion of citizen for one article
|
||||
* Get paginated opinions of citizen for all articles
|
||||
*/
|
||||
@Location("/citizens/{citizen}/opinions/articles")
|
||||
class CitizenOpinionArticleRequest(
|
||||
@@ -36,16 +44,37 @@ object OpinionArticlePaths {
|
||||
/**
|
||||
* Put an opinion on one article
|
||||
*/
|
||||
@Location("/articles/{article}/opinons")
|
||||
class ArticleOpinion(val article: ArticleEntity)
|
||||
@Location("/articles/{article}/opinions")
|
||||
@KtorExperimentalAPI
|
||||
class ArticleOpinion(val article: ArticleEntity) : RequestBuilder<OpinionArticle> {
|
||||
|
||||
private class Content(
|
||||
opinionChoice: String
|
||||
) : KoinComponent {
|
||||
val opinionChoice = OpinionChoiceRef(opinionChoice.toUUID())
|
||||
|
||||
fun create(citizen: Citizen, article: ArticleEntity): OpinionArticle {
|
||||
return OpinionArticle(
|
||||
choice = get<OpinionChoice>().findOpinionChoiceById(opinionChoice.id) ?: throw BadRequestException("OpinionChoice not exist: id(${opinionChoice.id})"),
|
||||
target = article,
|
||||
createdBy = citizen
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getContent(call: ApplicationCall): OpinionArticle {
|
||||
return call.receive<Content>().create(call.citizen, article)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Opinion of citizen on targets by target ids
|
||||
*/
|
||||
@Location("/citizen/{citizen}/opinions")
|
||||
class CitizenOpinions(val citizen: CitizenEntity, id: List<String>): KoinComponent {
|
||||
@Location("/citizens/{citizen}/opinions")
|
||||
class CitizenOpinions(val citizen: CitizenEntity, id: List<String>) : KoinComponent {
|
||||
val id: List<UUID> = id.toUUID()
|
||||
val opinionsEntities = get<OpinionArticleRepository>()
|
||||
.findCitizenOpinionsByTargets(citizen, id.toUUID())
|
||||
.findCitizenOpinionsByTargets(citizen, this.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +93,12 @@ fun Route.opinionArticle(repo: OpinionArticleRepository) {
|
||||
}
|
||||
|
||||
put<OpinionArticlePaths.ArticleOpinion> {
|
||||
val optionArticle = call.receive<ArticleOpinionRequest>().create(citizen)
|
||||
assertCan(VIEW, optionArticle)
|
||||
|
||||
call.respond(HttpStatusCode.Created, optionArticle)
|
||||
call.getContent(it)
|
||||
.let { opinion ->
|
||||
assertCan(VIEW, opinion)
|
||||
repo.opinion(opinion)
|
||||
}.let {
|
||||
call.respond(HttpStatusCode.Created, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package fr.dcproject.routes
|
||||
|
||||
import fr.dcproject.entity.OpinionChoice
|
||||
import fr.dcproject.security.voter.OpinionVoter.Action.VIEW
|
||||
import fr.dcproject.security.voter.OpinionChoiceVoter.Action.VIEW
|
||||
import fr.dcproject.security.voter.assertCan
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -17,7 +17,7 @@ object OpinionChoicePaths {
|
||||
class OpinionChoiceRequest(val opinionChoice: OpinionChoice)
|
||||
|
||||
@Location("/opinions")
|
||||
class OpinionChoicesRequest(val targets: List<String>)
|
||||
class OpinionChoicesRequest(val targets: List<String> = emptyList())
|
||||
}
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
|
||||
@@ -37,9 +37,10 @@ class OpinionVoter : Voter {
|
||||
}
|
||||
|
||||
if (action == Action.DELETE) {
|
||||
return if (subject is Opinion<*>
|
||||
&& user != null
|
||||
&& subject.createdBy.user.id == user.id)
|
||||
return if (subject is Opinion<*> &&
|
||||
user != null &&
|
||||
subject.createdBy.user.id == user.id
|
||||
)
|
||||
Vote.GRANTED
|
||||
else Vote.DENIED
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user