create SQL function for get citizen votes with multiple target ids

add updated_at field on vote table
This commit is contained in:
2019-10-03 15:43:39 +02:00
parent 51cc5b640d
commit c5a2f92b19
8 changed files with 134 additions and 31 deletions

View File

@@ -31,21 +31,6 @@ open class Vote <T: UuidEntity>(override var requester: Requester): RepositoryI<
)!! )!!
} }
fun findByCitizen(
citizen: CitizenEntity,
target: String,
typeReference: TypeReference<List<VoteEntity<T>>>,
page: Int = 1,
limit: Int = 50
): Paginated<VoteEntity<T>> =
findByCitizen(
citizen.id ?: error("The citizen must have an id"),
target,
typeReference,
page,
limit
)
fun findByCitizen( fun findByCitizen(
citizenId: UUID, citizenId: UUID,
target: String, target: String,
@@ -61,6 +46,21 @@ open class Vote <T: UuidEntity>(override var requester: Requester): RepositoryI<
)) ))
} }
} }
fun findCitizenVotesByTargets(
citizen: CitizenEntity,
targets: List<UUID>
): List<VoteEntity<*>> {
val typeReference = object: TypeReference<List<VoteEntity<UuidEntity>>>() {}
return requester.run {
val citizenId = citizen.id ?: error("The citizen must have an id")
getFunction("find_citizen_votes_by_target_ids")
.select(typeReference, mapOf(
"citizen_id" to citizenId,
"ids" to targets
))
}
}
} }
class VoteArticle (requester: Requester): Vote<Article>(requester) { class VoteArticle (requester: Requester): Vote<Article>(requester) {
@@ -70,7 +70,7 @@ class VoteArticle (requester: Requester): Vote<Article>(requester) {
limit: Int = 50 limit: Int = 50
): Paginated<VoteEntity<Article>> = ): Paginated<VoteEntity<Article>> =
findByCitizen( findByCitizen(
citizen, citizen.id ?: error("The citizen must have an id"),
"article", "article",
object: TypeReference<List<VoteEntity<Article>>>() {}, object: TypeReference<List<VoteEntity<Article>>>() {},
page, page,
@@ -85,7 +85,7 @@ class VoteConstitution (requester: Requester): Vote<Constitution>(requester) {
limit: Int = 50 limit: Int = 50
): Paginated<VoteEntity<Constitution>> = ): Paginated<VoteEntity<Constitution>> =
findByCitizen( findByCitizen(
citizen, citizen.id ?: error("The citizen must have an id"),
"constitution", "constitution",
object: TypeReference<List<VoteEntity<Constitution>>>() {}, object: TypeReference<List<VoteEntity<Constitution>>>() {},
page, page,

View File

@@ -15,6 +15,7 @@ import io.ktor.locations.put
import io.ktor.request.receive import io.ktor.request.receive
import io.ktor.response.respond import io.ktor.response.respond
import io.ktor.routing.Route import io.ktor.routing.Route
import java.util.*
import fr.dcproject.entity.Article as ArticleEntity import fr.dcproject.entity.Article as ArticleEntity
import fr.dcproject.entity.Vote as VoteEntity import fr.dcproject.entity.Vote as VoteEntity
import fr.dcproject.repository.VoteArticle as VoteArticleRepository import fr.dcproject.repository.VoteArticle as VoteArticleRepository
@@ -33,6 +34,14 @@ object VoteArticlePaths {
limit: Int = 50, limit: Int = 50,
val search: String? = null val search: String? = null
): PaginatedRequestI by PaginatedRequest(page, limit) ): PaginatedRequestI by PaginatedRequest(page, limit)
@Location("/citizens/{citizen}/votes")
class CitizenVotesByIdsRequest(val citizen: Citizen, id: List<String>) {
val id: List<UUID> = id
.map { it.trim() }
.filter { it.isNotBlank() }
.map { UUID.fromString(it) }
}
} }
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
@@ -55,4 +64,11 @@ fun Route.voteArticle(repo: VoteArticleRepository) {
call.respond(votes) call.respond(votes)
} }
get<VoteArticlePaths.CitizenVotesByIdsRequest> {
val votes = repo.findCitizenVotesByTargets(it.citizen, it.id)
assertCan(VIEW, votes)
call.respond(votes)
}
} }

View File

@@ -62,14 +62,7 @@ paths:
- citizen - citizen
operationId: getCitizen operationId: getCitizen
parameters: parameters:
- name: citizen - $ref: '#/components/parameters/citizen'
in: path
description: ID of citizen
example: e74be8e4-6823-47c4-bd1b-789725b2fa8e
required: true
schema:
type: string
format: uuid
responses: responses:
200: 200:
description: The Citizen object description: The Citizen object
@@ -79,6 +72,40 @@ paths:
$ref: '#/components/schemas/CitizenResponse' $ref: '#/components/schemas/CitizenResponse'
404: 404:
description: Citizen not found description: Citizen not found
/citizens/{citizen}/votes:
get:
security:
- JWTAuth: []
summary: Get Citizen
tags:
- citizen
operationId: getCitizenVotes
parameters:
- $ref: '#/components/parameters/citizen'
- name: id
in: query
required: true
example:
- 1329ab90-edae-cfed-f863-c8cb069fa327
- cab54e50-ce85-bba0-da23-fc9f75feeaf5
schema:
type: array
items:
type: string
format: uuid
responses:
200:
description: The Votes objects
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/VoteResponse'
404:
description: Citizen not found
/citizens: /citizens:
get: get:
security: security:
@@ -609,6 +636,16 @@ components:
schema: schema:
type: string type: string
citizen:
name: citizen
in: path
description: ID of citizen
example: 770d46e8-c458-417d-beaf-0f2faa109b26
required: true
schema:
type: string
format: uuid
securitySchemes: securitySchemes:
JWTAuth: JWTAuth:
type: http type: http

View File

@@ -0,0 +1,32 @@
create or replace function find_citizen_votes_by_target_ids(
_citizen_id uuid,
_ids uuid[],
_reference regclass default null,
out resource json
) language plpgsql as
$$
begin
select
json_agg(t)
into resource
from (
select
v.*,
find_reference_by_id(v.target_id, _reference) as target,
find_citizen_by_id(v.created_by_id) as created_by
from vote as v
where
(_reference is null or _reference = target_reference)
and target_id = any(_ids)
and created_by_id = _citizen_id
order by
_ids
limit 100
) as t;
end;
$$;
-- drop function if exists find_citizen_votes_by_target_ids(uuid, uuid[], regclass);

View File

@@ -7,25 +7,29 @@ begin
values (_created_by_id, _target_id, _note, _anonymous) values (_created_by_id, _target_id, _note, _anonymous)
on conflict (created_by_id, target_id) do update set on conflict (created_by_id, target_id) do update set
note = excluded.note, note = excluded.note,
anonymous = excluded.anonymous; anonymous = excluded.anonymous,
updated_at = now();
elseif reference = 'constitution'::regclass then elseif reference = 'constitution'::regclass then
insert into vote_for_constitution (created_by_id, target_id, note, anonymous) insert into vote_for_constitution (created_by_id, target_id, note, anonymous)
values (_created_by_id, _target_id, _note, _anonymous) values (_created_by_id, _target_id, _note, _anonymous)
on conflict (created_by_id, target_id) do update set on conflict (created_by_id, target_id) do update set
note = excluded.note, note = excluded.note,
anonymous = excluded.anonymous; anonymous = excluded.anonymous,
updated_at = now();
elseif reference = 'comment_on_article'::regclass then elseif reference = 'comment_on_article'::regclass then
insert into vote_for_comment_on_article (created_by_id, target_id, note, anonymous) insert into vote_for_comment_on_article (created_by_id, target_id, note, anonymous)
values (_created_by_id, _target_id, _note, _anonymous) values (_created_by_id, _target_id, _note, _anonymous)
on conflict (created_by_id, target_id) do update set on conflict (created_by_id, target_id) do update set
note = excluded.note, note = excluded.note,
anonymous = excluded.anonymous; anonymous = excluded.anonymous,
updated_at = now();
elseif reference = 'comment_on_constitution'::regclass then elseif reference = 'comment_on_constitution'::regclass then
insert into vote_for_comment_on_constitution (created_by_id, target_id, note, anonymous) insert into vote_for_comment_on_constitution (created_by_id, target_id, note, anonymous)
values (_created_by_id, _target_id, _note, _anonymous) values (_created_by_id, _target_id, _note, _anonymous)
on conflict (created_by_id, target_id) do update set on conflict (created_by_id, target_id) do update set
note = excluded.note, note = excluded.note,
anonymous = excluded.anonymous; anonymous = excluded.anonymous,
updated_at = now();
else else
raise exception '% no implemented', reference::text; raise exception '% no implemented', reference::text;
end if; end if;

View File

@@ -450,8 +450,9 @@ execute procedure set_comment_parents_ids();
create table vote create table vote
( (
anonymous boolean default true not null, updated_at timestamptz default now() not null check ( updated_at >= created_at ),
note int not null check ( note >= -1 and note <= 1 ), anonymous boolean default true not null,
note int not null check ( note >= -1 and note <= 1 ),
foreign key (created_by_id) references citizen (id), foreign key (created_by_id) references citizen (id),
primary key (id), primary key (id),
unique (created_by_id, target_id) unique (created_by_id, target_id)

View File

@@ -29,3 +29,10 @@ Feature: vote Article
| limit | 50 | | limit | 50 |
| total | 1 | | total | 1 |
| result[0].note | 1 | | result[0].note | 1 |
Scenario: Can get votes of current citizen by target ids
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
When I send a GET request to "/citizens/64b7b379-2298-43ec-b428-ba134930cabd/votes?id=9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b&id=64b1f265-bfb3-332b-eef9-d00f63a3beaa"
Then the response status code should be 200
And the response should contain object:
| [0].note | 1 |

View File

@@ -38,6 +38,7 @@ declare
$json$; $json$;
votes jsonb; votes jsonb;
votes_of_citizen json; votes_of_citizen json;
votes_of_citizen_for_targets json;
votes_total int; votes_total int;
begin begin
-- insert user for context -- insert user for context
@@ -97,6 +98,11 @@ begin
assert (votes_total = 1), format('votes count for user %s must be 1, instead of %s', _citizen_id, votes_total); assert (votes_total = 1), format('votes count for user %s must be 1, instead of %s', _citizen_id, votes_total);
assert ((votes_of_citizen#>>'{0,note}')::int = -1), format('the note must be -1, instead of %s', (votes_of_citizen#>>'{0,note}')); assert ((votes_of_citizen#>>'{0,note}')::int = -1), format('the note must be -1, instead of %s', (votes_of_citizen#>>'{0,note}'));
-- test "find_citizen_votes_by_target_ids"
select find_citizen_votes_by_target_ids(_citizen_id, array[(created_article->>'id')]::uuid[]) into votes_of_citizen_for_targets;
assert (json_array_length(votes_of_citizen_for_targets) = 1), format('the function must be return 1 vote, instead of %s', json_array_length(votes_of_citizen_for_targets));
assert (votes_of_citizen_for_targets#>>'{0,target_id}' = created_article->>'id'), format('target_id of vote must be %s, instead of %s', created_article->>'id', votes_of_citizen_for_targets#>>'{0,target_id}');
-- delete vote and context -- delete vote and context
delete from vote; delete from vote;
delete from article; delete from article;