From eb7a0c7210e5e652937db3979e3428da6154bc82 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Wed, 5 Feb 2020 15:20:38 +0100 Subject: [PATCH] Add sort on comments -> findByTarget --- .idea/misc.xml | 5 ++- .../kotlin/fr/dcproject/repository/Comment.kt | 32 ++++++++++++++----- .../fr/dcproject/routes/CommentArticle.kt | 7 ++-- src/main/resources/openApi.yaml | 16 ++++++++++ .../comment/find_comments_by_target.sql | 11 ++++++- .../sql/functions/vote/count_vote.sql | 5 ++- .../resources/feature/commentArticle.feature | 16 +++++++--- 7 files changed, 75 insertions(+), 17 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 5cd2caa..e2668c2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,10 +1,13 @@ + + + - + \ No newline at end of file diff --git a/src/main/kotlin/fr/dcproject/repository/Comment.kt b/src/main/kotlin/fr/dcproject/repository/Comment.kt index 89a3547..fcc196f 100644 --- a/src/main/kotlin/fr/dcproject/repository/Comment.kt +++ b/src/main/kotlin/fr/dcproject/repository/Comment.kt @@ -46,21 +46,24 @@ abstract class Comment(override var requester: Requester) : Reposit open fun findByTarget( target: UuidEntityI, page: Int = 1, - limit: Int = 50 + limit: Int = 50, + sort: CommentArticle.Sort = CommentArticle.Sort.CREATED_AT ): Paginated> { - return findByTarget(target.id, page, limit) + return findByTarget(target.id, page, limit, sort) } open fun findByTarget( targetId: UUID, page: Int = 1, - limit: Int = 50 + limit: Int = 50, + sort: CommentArticle.Sort = CommentArticle.Sort.CREATED_AT ): Paginated> { return requester.run { getFunction("find_comments_by_target") .select( page, limit, - "target_id" to targetId + "target_id" to targetId, + "sort" to sort.sql ) } } @@ -145,16 +148,27 @@ class CommentArticle(requester: Requester) : Comment(requester) { override fun findByTarget( target: UuidEntityI, page: Int, - limit: Int + limit: Int, + sort: Sort ): Paginated> { return requester.run { getFunction("find_comments_by_target") .select( page, limit, - "target_id" to target.id + "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 } + } + } + } } class CommentConstitution(requester: Requester) : Comment(requester) { @@ -182,13 +196,15 @@ class CommentConstitution(requester: Requester) : Comment(reque override fun findByTarget( target: UuidEntityI, page: Int, - limit: Int + limit: Int, + sort: CommentArticle.Sort ): Paginated> { return requester.run { getFunction("find_comments_by_target") .select( page, limit, - "target_id" to target.id + "target_id" to target.id, + "sort" to sort.sql ) } } diff --git a/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt b/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt index befb1d2..0b184fd 100644 --- a/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt +++ b/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt @@ -3,6 +3,7 @@ package fr.dcproject.routes import fr.dcproject.citizen import fr.dcproject.entity.ArticleRef import fr.dcproject.entity.Citizen +import fr.dcproject.repository.CommentArticle.Sort import fr.dcproject.security.voter.CommentVoter.Action.CREATE import fr.dcproject.security.voter.CommentVoter.Action.VIEW import fr.dcproject.security.voter.assertCan @@ -26,10 +27,12 @@ object CommentArticlePaths { val article: ArticleRef, page: Int = 1, limit: Int = 50, - val search: String? = null + val search: String? = null, + sort: String = Sort.CREATED_AT.sql ) { val page: Int = if (page < 1) 1 else page val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit + val sort: Sort = Sort.fromString(sort) ?: Sort.CREATED_AT } @Location("/citizens/{citizen}/comments/articles") @@ -39,7 +42,7 @@ object CommentArticlePaths { @KtorExperimentalLocationsAPI fun Route.commentArticle(repo: CommentArticleRepository) { get { - val comment = repo.findByTarget(it.article, it.page, it.limit) + val comment = repo.findByTarget(it.article, it.page, it.limit, it.sort) assertCan(VIEW, comment.result) call.respond(HttpStatusCode.OK, comment) } diff --git a/src/main/resources/openApi.yaml b/src/main/resources/openApi.yaml index 991eb3f..42dc61a 100644 --- a/src/main/resources/openApi.yaml +++ b/src/main/resources/openApi.yaml @@ -449,6 +449,22 @@ paths: tags: - comment - article + parameters: + - $ref: '#/components/parameters/page' + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/search' + - name: sort + in: query + required: false + example: + - created_at + - votes + schema: + type: string + default: created_at + enum: + - created_at + - votes responses: 200: description: Return Comment and children diff --git a/src/main/resources/sql/functions/comment/find_comments_by_target.sql b/src/main/resources/sql/functions/comment/find_comments_by_target.sql index 3c0b5ed..b4ede4a 100644 --- a/src/main/resources/sql/functions/comment/find_comments_by_target.sql +++ b/src/main/resources/sql/functions/comment/find_comments_by_target.sql @@ -2,6 +2,7 @@ create or replace function find_comments_by_target( _target_id uuid, "limit" int default 50, "offset" int default 0, + "sort" text default 'created_at', out resource json, out total int ) language plpgsql as @@ -18,7 +19,15 @@ begin count_vote(com.id) as votes from "comment" as com where com.target_id = _target_id - order by created_at asc, + order by + case sort + when 'votes' then (count_vote(com.id)->>'percent')::int + else null + end desc, + case sort + when 'created_at' then com.created_at::text + else null + end desc, com.created_at desc limit "limit" offset "offset" ) as t; diff --git a/src/main/resources/sql/functions/vote/count_vote.sql b/src/main/resources/sql/functions/vote/count_vote.sql index 5a04da8..6e507b3 100644 --- a/src/main/resources/sql/functions/vote/count_vote.sql +++ b/src/main/resources/sql/functions/vote/count_vote.sql @@ -5,6 +5,7 @@ declare agg jsonb; empty jsonb = '{"down":0,"neutral":0,"up":0,"total":0,"score":0}'::jsonb; score int; + percent numeric; total int; begin select jsonb_object_agg(t.label, t.total) @@ -25,12 +26,14 @@ begin agg = empty || coalesce(agg, empty); score = ((agg->>'up')::int - (agg->>'down')::int); + percent = ((agg->>'up')::int - (agg->>'down')::int); total = ((agg->>'up')::int + (agg->>'down')::int + (agg->>'neutral')::int); resource = agg || jsonb_build_object('updated_at', now()) || jsonb_build_object('total', total) || - jsonb_build_object('score', score); + jsonb_build_object('score', score) || + jsonb_build_object('percent', percent); end; $$; diff --git a/src/test/resources/feature/commentArticle.feature b/src/test/resources/feature/commentArticle.feature index d548899..7016ed1 100644 --- a/src/test/resources/feature/commentArticle.feature +++ b/src/test/resources/feature/commentArticle.feature @@ -17,6 +17,14 @@ Feature: comment Article When I send a GET request to "/articles/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/comments" Then the response status code should be 200 + Scenario: Can get all comment on article sorted by votes + Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" + And I have article with id "9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b" + When I send a GET request to "/articles/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/comments?sort=votes" + Then the response status code should be 200 + And the response should contain object: + | result[1].votes.up | 1 | + Scenario: Can get comments on articles of the current citizen Given I have citizen John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" And I have article with id "9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b" @@ -33,11 +41,11 @@ Feature: comment Article Hello boy """ Then the response status code should be 200 - And the JSON should contain: - | content | Hello boy | + And the JSON should contain: + | content | Hello boy | Scenario: Can get comment by its ID When I send a GET request to "/comments/2f01c257-cf20-3466-fb10-a3b8eff12a97" Then the response status code should be 200 - And the JSON should contain: - | content | Hello boy | + And the JSON should contain: + | content | Hello boy |